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.