71

TL;DR

What is the best way to forcibly keep a Node.js process running, i.e., keep its event loop from running empty and hence keeping the process from terminating? The best solution I could come up with was this:

const SOME_HUGE_INTERVAL = 1 << 30;
setInterval(() => {}, SOME_HUGE_INTERVAL);

Which will keep an interval running without causing too much disturbance if you keep the interval period long enough.

Is there a better way to do it?

Long version of the question

I have a Node.js script using Edge.js to register a callback function so that it can be called from inside a DLL in .NET. This function will be called 1 time per second, sending a simple sequence number that should be printed to the console.

The Edge.js part is fine, everything is working. My only problem is that my Node.js process executes its script and after that it runs out of events to process. With its event loop empty, it just terminates, ignoring the fact that it should've kept running to be able to receive callbacks from the DLL.

My Node.js script:

var
    edge = require('edge');

var foo = edge.func({
    assemblyFile: 'cs.dll',
    typeName: 'cs.MyClass',
    methodName: 'Foo'
});

// The callback function that will be called from C# code:
function callback(sequence) {
    console.info('Sequence:', sequence);
}

// Register for a callback:
foo({ callback: callback }, true);

// My hack to keep the process alive:
setInterval(function() {}, 60000);

My C# code (the DLL):

public class MyClass
{
    Func<object, Task<object>> Callback;

    void Bar()
    {
        int sequence = 1;

        while (true)
        {
            Callback(sequence++);
            Thread.Sleep(1000);
        }
    }

    public async Task<object> Foo(dynamic input)
    {
        // Receives the callback function that will be used:
        Callback = (Func<object, Task<object>>)input.callback;

        // Starts a new thread that will call back periodically:
        (new Thread(Bar)).Start();

        return new object { };
    }
}

The only solution I could come up with was to register a timer with a long interval to call an empty function just to keep the scheduler busy and avoid getting the event loop empty so that the process keeps running forever.

Is there any way to do this better than I did? I.e., keep the process running without having to use this kind of "hack"?

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104
  • 1
    You could start a bogus listener on a socket on the node side to keep your process alive. – Joe May 13 '14 at 03:35
  • JXcore (a node.js distro) has process.keepAlive and process.release . You could call process.keepAlive() prior to everything else and finally process.release() whenever the application needs to be closed. – Nuray Altin May 13 '14 at 14:09
  • Thanks, @NurayAltin. I don't plan migrating to JXCore now, but it's good to know it has an API to do that. – Lucio Paiva May 13 '14 at 16:51
  • Unless I'm mistaken, you are looking for `Number.POSITIVE_INFINITY` or simply the `Infinity` global. I don't believe there is a `POSITIVE_INFINITY` property in the global `Math` object. – rich remer Aug 25 '16 at 18:26
  • You're right, @richremer. Just fixed it, thanks. – Lucio Paiva Aug 26 '16 at 20:28
  • Updated the question to reflect the fact that `Number.POSITIVE_INFINITY` can't be used anymore (see docs [here](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) and [here](https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args)). – Lucio Paiva May 26 '18 at 16:52

6 Answers6

59

The simplest, least intrusive solution

I honestly think my approach is the least intrusive one:

setInterval(() => {}, 1 << 30);

This will set a harmless interval that will fire approximately once every 12 days, effectively doing nothing, but keeping the process running.

Originally, my solution used Number.POSITIVE_INFINITY as the period, so the timer would actually never fire, but this behavior was recently changed by the API and now it doesn't accept anything greater than 2147483647 (i.e., 2 ** 31 - 1). See docs here and here.


Comments on other solutions

For reference, here are the other two answers given so far:

Joe's (deleted since then, but perfectly valid):

require('net').createServer().listen();

Will create a "bogus listener", as he called it. A minor downside is that we'd allocate a port just for that.

Jacob's:

process.stdin.resume();

Or the equivalent:

process.stdin.on("data", () => {});

Puts stdin into "old" mode, a deprecated feature that is still present in Node.js for compatibility with scripts written prior to Node.js v0.10 (reference).

I'd advise against it. Not only it's deprecated, it also unnecessarily messes with stdin.

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104
  • Just a quick note that while setInterval is "less intrusive", it does require SIGINT to be sent to the process. The stdin solutions allow you to exit normally by simply sending EOF via ctrl+d. – Cody Allan Taylor Feb 19 '19 at 06:53
  • Are you sure about that? Cause AFAIK setInterval is handled internally by libuv. There should be no need for external signals to handle timers whatsoever. – Lucio Paiva Feb 19 '19 at 12:18
  • I am assuming that no code was written to clear the timer. Which means the way most users would stop the program is ctrl+c, which sends SIGINT. – Cody Allan Taylor Feb 19 '19 at 14:07
  • Right, but we may be talking about different things. My question was about preventing Node's event loop from running empty and thus finishing the process. It would be fine if the user decided to terminate the process in whatever way though. I just don't want the process to terminate by itself. – Lucio Paiva Feb 19 '19 at 14:35
  • Sweet! I just thought of something though. Im not an expert on Libuv or event loops (I do know a little) but this solution may still not be as good as if there were a natural way for direct access to the event loop (which node doesnt seem to provide). By direct access I mean: a way to either directly configure the behavior of the event loop or a way to add a custom "referenced" Libuv handle directly to the event loop (e.g. as can be done via Neovim's `vim.loop`). Whereas this solution may be requiring an *additional* comparison each turn of the loop (in order to check if timer should fire yet) – tmillr Nov 12 '22 at 02:55
  • Hey @tmillr, I'm no expert on Libuv either, but I'm pretty sure they must use some sort of clever data structure for that, like a priority queue. No matter how many events are scheduled, the algorithm only needs to check the top one (i.e,, the one with the higher priority). So you can rest assured it won't incur any performance penalties in your application. – Lucio Paiva Nov 13 '22 at 18:15
  • @LucioPaiva I suppose I was envisioning a program which is completely idle, and then comparing the performance cost of adding a timer to such a program (& now the timer needs to be checked/polled by the event loop, or perhaps Libuv timers are handled by the OS instead? but I don't think thats the case & even then the loop would need to poll for events from the OS). Anyway Im just realizing now that such a program (one that is completely asleep/idle) is a useless program & isn't a valid hypothetical program to compare against as it would be totally useless & no different from a non-running one. – tmillr Nov 14 '22 at 04:31
40

Use "old" Streams mode to listen for a standard input that will never come:

// Start reading from stdin so we don't exit.
process.stdin.resume();
randers
  • 5,031
  • 5
  • 37
  • 64
Jacob Krall
  • 28,341
  • 6
  • 66
  • 76
  • "...to listen for a standard input that will never come" mmmmm watchya sayyyyyyyyy (sorry I had to) – tmillr Nov 14 '22 at 04:33
1

Here is IFFE based on the accepted answer:

(function keepProcessRunning() {
  setTimeout(keepProcessRunning, 1 << 30);
})();

and here is conditional exit:

let flag = true;
(function keepProcessRunning() {
  setTimeout(() => flag && keepProcessRunning(), 1000);
})();
BitOfUniverse
  • 5,903
  • 1
  • 34
  • 38
0

You could use a setTimeout(function() {""},1000000000000000000); command to keep your script alive without overload.

9pfs
  • 560
  • 5
  • 17
0

spin up a nice repl, node would do the same if it didn't receive an exit code anyway:

import("repl").then(repl=>
repl.start({prompt:"\x1b[31m"+process.versions.node+": \x1b[0m"}));
bpstrngr
  • 339
  • 3
  • 13
-2

I'll throw another hack into the mix. Here's how to do it with Promise:

new Promise(_ => null);

Throw that at the bottom of your .js file and it should run forever.

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104
Micah
  • 17,584
  • 8
  • 40
  • 46