Dealing with process signals with Node.js

  • Post by Nicolas Ramz
  • Apr 13, 2018
Dealing with process signals with Node.js

JavaScript is asynchronous but we sometimes forget that it’s single threaded. This can be a problem when dealing with signals.

Today I stumbled a problem while developping cat-hex, a small command line tool for Node.js.

Cat-hex shows hexadecimal dump of any binary (or text) file:

$ ch -b 32 ./file.zip
00000000   504b0304  14000000  000093b8  2a4a0000  00000000  00000000  PK..........*J..........
00000018   00000900  0000416d  6269616e  63652f50  4b030414  00000000  ......Ambiance/PK.......
00000030   0093b82a  4a000000  00000000  00000000  000a0000  00416d62  ...*J................Amb
00000048   69616e63  65332f50  4b030414  00000008  00ba024e  33e6233a  iance3/PK..........N3.#:
00000060   a3e77c48  00825749  00140000  00416d62  69616e63  65332f46  ..|H..WI.....Ambiance3/F
00000078   6f726573  742e6d70  33245a75  58935f1b  7e17202d  31e24739  orest.mp3$ZuX._.~. -1.G9
00000090   3aa4414a  d0d18c92  464a4905  69105140  193d4677  77232229  :.AJ....FJI.i.Q@.=Fww#")
000000a8   824a4977  49084a77  a9804abe  dff07bfe  d9ae5dd7  ce799e73  .JIwI.Jw..J...{...]..y.s
000000c0   3f799f03  9ec6a118  10c09568  12fcfb20  b6ca0300  72f94895  ?y.........h... ....r.H.
000000d8   f45b00b1  7cae4323  f7280005  306d9400  1c809289  c8114551  .[..|.C#.(..0m........EQ

While this works as expected, I quickly noticed a little problem when using pipe to pipe cat-hex’s output to other commands, like more:

$ ch -b 32 ./file.zip | more

It worked, but pressing q to exit more won’t return to the command prompt, as expected.

My first guess was that I needed to handle some signal SIGPIPE so I added these lines:

process.on('SIGPIPE', () => {
    if (hexa) {
        hexa.cleanup();
    }
    process.exit();
});

Unfortunately, this did not fix the problem, the app still appeared to be stuck when I exited more.

While leaving the command line as is, I then noticed that after some long delay, the command actually exited, and returned to the bash prompt.

As I’m quite new to Unix command line and native programming in general - I did some C++ apps but that was ages ago! - I wasn’t sure what was going on after using pipe to send the content of an app to another one.

What seems to happen is that the app’s output - so is its execution - is kinda paused by more, and when exiting, the pipe is broken, which makes the app’s execution resume.

That’s why cat-hex didn’t immediately return to bash, as it was still reading the file’s content.

What was misleading is the fact that while it’s running, the output doesn’t appear.

Back to the Node.js code, the problem lies in this piece of code:

for (var i = 0; i < max; ++i) {
    // ...
    printline();
    // ...
}

This code is executed until the end of the file is reached.

The problem is that since the JavaScript is a single threaded language, when my app receives a SIGPIPE signal, it’s actually put inside the message queue.

And this message queue is only flushed after the code in the main (and only) thread has been executed.

This explains why I didn’t receive the message right after exiting more and why my app only exited after a long delay (which depended on the size of the file I was reading).

To fix the problem, I had to rely on setImmediate:

Schedules the "immediate" execution of the callback after I/O events' callbacks. Returns an Immediate for use with clearImmediate().

By calling this function, cat-hex’s execution will be postponed until the message queue is empty and the SIGPIPE handle that was added us actually called:

for (var i = 0; i < max; ++i) {
    // ...
    printline();
    // ...
}

becomes:

    // ...
    printline() {
        if (i < max) {
           // schedules printing the next line right after I/O events have been handled 
           setImmediate(() => printline();
        }
    }
    printline();
    // ...
}

Update: Under Windows the pipe handling is a bit different. I had to add a special case because the SIGPIPE isn’t sent, and an error is thrown instead:

process.stdout.on('error', (err) => {
    if (err.code == "EPIPE") {
        // cleanup code here
    }
});

Source: Stack Overflow

More information about cat-hex can be found on GitHub.