{"_id":"exeunt","_rev":"1391626","name":"exeunt","description":"exiting a node.js process *and flushing stdout and stderr*","dist-tags":{"latest":"1.1.2"},"maintainers":[{"name":"bahamat","email":""},{"name":"jgilli","email":"jgilli@fastmail.fm"},{"name":"melloc","email":""},{"name":"trentm","email":""}],"time":{"modified":"2021-11-03T23:41:04.000Z","created":"2017-03-11T00:31:16.265Z","1.1.2":"2021-11-03T23:40:52.344Z","1.1.1":"2017-05-03T16:03:56.214Z","1.1.0":"2017-03-17T17:50:29.207Z","1.0.0":"2017-03-11T00:31:16.265Z"},"users":{},"author":{"name":"Joyent","url":"joyent.com"},"repository":{"type":"git","url":"git+https://github.com/joyent/node-exeunt.git"},"versions":{"1.1.2":{"name":"exeunt","description":"exiting a node.js process *and flushing stdout and stderr*","version":"1.1.2","author":{"name":"Joyent","url":"joyent.com"},"homepage":"https://github.com/joyent/node-exeunt","keywords":["exit","flush","stdout","stderr","cli"],"main":"./lib/exeunt.js","repository":{"type":"git","url":"git+https://github.com/joyent/node-exeunt.git"},"engines":{"node":">=0.10"},"license":"MPL-2.0","devDependencies":{"eslint":"3.x"},"gitHead":"61cd76cfa2e0fcce90d1adb8316ea94e502f54a9","bugs":{"url":"https://github.com/joyent/node-exeunt/issues"},"_id":"exeunt@1.1.2","_nodeVersion":"12.22.7","_npmVersion":"6.14.13","dist":{"shasum":"e38f542a144084ef1ef6e231c197860ab10b8b39","size":6474,"noattachment":false,"key":"/exeunt/-/exeunt-1.1.2.tgz","tarball":"http://registry.cnpm.dingdandao.com/exeunt/download/exeunt-1.1.2.tgz"},"_npmUser":{"name":"bahamat","email":"bahamat@digitalelf.net"},"directories":{},"maintainers":[{"name":"bahamat","email":""},{"name":"jgilli","email":"jgilli@fastmail.fm"},{"name":"melloc","email":""},{"name":"trentm","email":""}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/exeunt_1.1.2_1635982852162_0.15638857027758668"},"_hasShrinkwrap":false,"publish_time":1635982852344,"_cnpm_publish_time":1635982852344},"1.1.1":{"name":"exeunt","description":"exiting a node.js process *and flushing stdout and stderr*","version":"1.1.1","author":{"name":"Joyent","url":"joyent.com"},"homepage":"https://github.com/joyent/node-exeunt","keywords":["exit","flush","stdout","stderr","cli"],"main":"./lib/exeunt.js","repository":{"type":"git","url":"git://github.com/joyent/node-exeunt.git"},"engines":{"node":">=0.10"},"license":"MPL-2.0","devDependencies":{"eslint":"3.x"},"gitHead":"1febdc4d604b546c4c69aad3d99e5a2e17ef6a05","bugs":{"url":"https://github.com/joyent/node-exeunt/issues"},"_id":"exeunt@1.1.1","scripts":{},"_shasum":"b4054e06c3ba5a6c4e19c173454cc316ea07de75","_from":".","_npmVersion":"2.15.11","_nodeVersion":"4.8.0","_npmUser":{"name":"trentm","email":"trentm@gmail.com"},"maintainers":[{"name":"bahamat","email":""},{"name":"jgilli","email":"jgilli@fastmail.fm"},{"name":"melloc","email":""},{"name":"trentm","email":""}],"dist":{"shasum":"b4054e06c3ba5a6c4e19c173454cc316ea07de75","size":6616,"noattachment":false,"key":"/exeunt/-/exeunt-1.1.1.tgz","tarball":"http://registry.cnpm.dingdandao.com/exeunt/download/exeunt-1.1.1.tgz"},"_npmOperationalInternal":{"host":"packages-18-east.internal.npmjs.com","tmp":"tmp/exeunt-1.1.1.tgz_1493827433503_0.2843120254110545"},"directories":{},"publish_time":1493827436214,"_cnpm_publish_time":1493827436214,"_hasShrinkwrap":false},"1.1.0":{"name":"exeunt","description":"exiting a node.js process *and flushing stdout and stderr*","version":"1.1.0","author":{"name":"Joyent","url":"joyent.com"},"homepage":"https://github.com/joyent/node-exeunt","keywords":["exit","flush","stdout","stderr","cli"],"main":"./lib/exeunt.js","repository":{"type":"git","url":"git://github.com/joyent/node-exeunt.git"},"engines":{"node":">=0.10"},"license":"MPL-2.0","devDependencies":{"eslint":"3.x"},"gitHead":"6373701a7075c2ffa3a5e3a56640c263470cf178","bugs":{"url":"https://github.com/joyent/node-exeunt/issues"},"_id":"exeunt@1.1.0","scripts":{},"_shasum":"af72db6f94b3cb75e921aee375d513049843d284","_from":".","_npmVersion":"2.15.1","_nodeVersion":"0.10.48","_npmUser":{"name":"trentm","email":"trentm@gmail.com"},"maintainers":[{"name":"bahamat","email":""},{"name":"jgilli","email":"jgilli@fastmail.fm"},{"name":"melloc","email":""},{"name":"trentm","email":""}],"dist":{"shasum":"af72db6f94b3cb75e921aee375d513049843d284","size":7247,"noattachment":false,"key":"/exeunt/-/exeunt-1.1.0.tgz","tarball":"http://registry.cnpm.dingdandao.com/exeunt/download/exeunt-1.1.0.tgz"},"_npmOperationalInternal":{"host":"packages-12-west.internal.npmjs.com","tmp":"tmp/exeunt-1.1.0.tgz_1489773028981_0.8360535325482488"},"directories":{},"publish_time":1489773029207,"_cnpm_publish_time":1489773029207,"_hasShrinkwrap":false},"1.0.0":{"name":"exeunt","description":"exiting a node.js process *and flushing stdout and stderr*","version":"1.0.0","author":{"name":"Joyent","url":"joyent.com"},"homepage":"https://github.com/joyent/node-exeunt","keywords":["exit","flush","stdout","stderr","cli"],"main":"./lib/exeunt.js","repository":{"type":"git","url":"git://github.com/joyent/node-exeunt.git"},"engines":{"node":">=0.10"},"license":"MPL-2.0","gitHead":"434dbaed7aa7fc244f4ccf0fc6f0d77614062c48","bugs":{"url":"https://github.com/joyent/node-exeunt/issues"},"_id":"exeunt@1.0.0","scripts":{},"_shasum":"c90c7b979053ded077409b662a17c537526b5e74","_from":".","_npmVersion":"2.15.11","_nodeVersion":"4.8.0","_npmUser":{"name":"trentm","email":"trentm@gmail.com"},"maintainers":[{"name":"bahamat","email":""},{"name":"jgilli","email":"jgilli@fastmail.fm"},{"name":"melloc","email":""},{"name":"trentm","email":""}],"dist":{"shasum":"c90c7b979053ded077409b662a17c537526b5e74","size":4981,"noattachment":false,"key":"/exeunt/-/exeunt-1.0.0.tgz","tarball":"http://registry.cnpm.dingdandao.com/exeunt/download/exeunt-1.0.0.tgz"},"_npmOperationalInternal":{"host":"packages-18-east.internal.npmjs.com","tmp":"tmp/exeunt-1.0.0.tgz_1489192274409_0.7875496896449476"},"directories":{},"publish_time":1489192276265,"_cnpm_publish_time":1489192276265,"_hasShrinkwrap":false}},"readme":"# node-exeunt\n\nA module for (and discussion on) exiting a node.js process *and flushing stdout\nand stderr*.\n\nSomewhere in the node.js 0.10 or 0.12 version range, and at least on certain\nplatforms including macOS and SmartOS, stdout and stderr stopped being\nblocking. That means that where with node.js 0.10 or before your script might\nwrite output and exit with `process.exit([CODE])`, with newer versions of\nnode.js your output to stdout and/or stderr *would sometimes not all get\nwritten* before the process exited. This is most commonly an annoyance for\ncommand-line tools written in node.js, especially when used in a pipeline where\nthe problem more often manifests itself. The issue is surprisingly (at least to\nme) complex. This repo will attempt to explain the tradeoffs with different\nsolutions and provide advice and one or more functions to use for exiting.\n\n\n## Usage\n\n```javascript\nvar exeunt = require('exeunt');\n\nfunction main() {\n    // ...\n\n    exeunt(code);   // flush stdout/stderr and exit\n    return;         // `exeunt` returns, unlike `process.exit`\n}\n```\n\nSee the [Solution 4](#solution-4-exeunt) section below for details.\n\nNote: `exeunt()` is a small function. If you don't want yet another node\ndependency, then feel free to just copy it to your repo.\n\n\n## The problem\n\nA [node.js\nscript](https://github.com/joyent/node-exeunt/blob/master/examples/write-65k-and-exit.js)\nwrites a lot of output (such that buffering occurs), [and then\nexits](https://github.com/joyent/node-exeunt/blob/master/examples/write-65k-and-exit.js#L15).\nNot all output will be written before the process terminates. E.g.:\n\n```\n$ node examples/write-65k-and-exit.js | grep meta\n[meta] start: writing 66560 bytes...\n                                        # 65k of output elided by the `grep`\n[meta] done                             # all output was emitted this time\n\n$ node examples/write-65k-and-exit.js | grep meta\n[meta] start: writing 66560 bytes...\n                                        # the final 'done' line is missing\n```\n\nThis example writes 65k to be more than the buffer size for a pipe (which is\n64k, at least on macOS, IIUC). If we increase that to ~1MB, it is more frequent\nthat output is truncated:\n\n```\n$ node examples/write-65k-and-exit.js 1000000 | grep meta\n[meta] start: writing 1000000 bytes...\n```\n\n\n## Solution 1: avoid process.exit\n\nSummary: Use `process.exitCode = code;` (added in node.js 0.12), do *not* use\n`process.exit([code])`, and ensure you have no active handles\n(`process._getActiveHandles()`).\n\nPros:\n- All stdout and stderr content will be written before the node.js process\n  exits. AFAIK this is the only solution that guarantees this.\n\nCons:\n- You need to be diligent about closing active handles (from `setTimeout`,\n  `setInterval`, open sockets, etc.) otherwise your script will hang on exit.\n- In node 0.10 (if you need to support it), there is no way to exit with a\n  non-zero exit code without `process.exit(code)`.\n\n\n[Example](https://github.com/joyent/node-exeunt/blob/master/examples/hang-because-active-handle.js)\nshowing an accidental hang on exit:\n\n```\n$ node examples/hang-because-active-handle.js | grep meta\n[meta] start: writing 66560 bytes...\n[meta] done\n[meta] this interval is still running\n[meta] this interval is still running\n[meta] this interval is still running\n^C\n```\n\nIf you need to support node 0.10, [here is a `softExit()`\nfunction](https://github.com/joyent/node-exeunt/blob/master/lib/exeunt.js#L26-L56)\nthat will use `process.exitCode` if the node version supports it, else fallback\nto `process.exit` if necessary (with the potential for truncation).\n\n\n## Solution 2: give it a few seconds, then play hardball\n\nSummary: Attempt to avoid process.exit, but set a timer to use it after a\nshort while if it looks like we are hanging.\n\nPros:\n- In correct operation, your script will write out all stdout/stderr before\n  exiting.\n\nCons:\n- If stdout/stderr takes more than 2s (or whatever timeout you choose) to\n  flush, then output will still be truncated. This is the main tradeoff to\n  avoid a hang.\n- This technique involves calling a function that doesn't synchronously\n  exit the process like `process.exit()` does. That means you need to handle\n  it returning and code still executing. That might be as simple as calling\n  `return;`, or it might be more difficult. It depends on your application's\n  code.\n\n\n[Example](https://github.com/joyent/node-exeunt/blob/master/examples/hardball-after-2s.js#L25-L33):\n\n```\n$ node examples/hardball-after-2s.js | grep meta\n[meta] start: writing 66560 bytes...\n[meta] done\n[meta] this interval is still running\n[meta] this interval is still running\n[meta] hardball exit, you had your chance\n```\n\n\n## Solution 3: set stdout/stderr to be blocking\n\nThis all started because stdout/stderr weren't blocking. Let's just set them\nto be blocking again.\n\nPros:\n- Stdout and stderr will be flushed as soon as your script writes to them.\n\nCons:\n- The *node event loop can block* if the other end of those pipes isn't reading!\n  This was a subtlety that surprised me.\n  See <https://gist.github.com/misterdjules/3aa4c77d8f881ffccba3b6e6f0050d03>\n  for an example showing this. (TODO: include those scripts in examples/ here.)\n\n[Example](https://github.com/joyent/node-exeunt/blob/master/examples/set-blocking-write-65k-and-exit.js#L8-L10):\n\n```\n$ node examples/set-blocking-write-65k-and-exit.js 1000000 | grep meta\n[meta] start: writing 1000000 bytes...\n[meta] done\n```\n\n\n## Solution 4: exeunt\n\nSet stdout/stderr to be blocking, but *only when about to exit*.\n\nUsage:\n\n```javascript\nvar exeunt = require('exeunt');\n\nfunction main() {\n    // ...\n\n    exeunt(code);   // flush stdout/stderr and exit\n    return;         // `exeunt` returns, unlike `process.exit`\n}\n```\n\nPros:\n- Stdout and stderr will *most likely* (see below) be flushed before exiting.\n- Because `exeunt()` is calling `process.exit()`, there is no special issue with\n  the node event loop blocking.\n\nCons:\n- `exeunt()` calls `process.exit()` *asynchronously* (in `setImmediate`), which\n  means you need to handle code still executing. Depending on how your code is\n  structured, that might just require calling `return;`.\n- `process.exit` is called in `setImmediate` to ensure that one more pass\n  through the event loop will flush stdout/stderr. That event loop pass will\n  also run timers (as part of `uv__run_timers()` in `uv_run()`). I.e. current\n  `setTimeout`s and `setIntervals` may run one more time. My expectation is that\n  this shouldn't be a practical concern for most programs, but it might be\n  for yours.\n\n\n[Example](https://github.com/joyent/node-exeunt/blob/master/examples/write-65k-and-exeunt.js#L17):\n\n```\n$ node examples/write-65k-and-exeunt.js 1000000 | grep meta\n[meta] start: writing 1000000 bytes...\n[meta] done\n```\n\nThe code, to show what is happening, is here:\n<https://github.com/joyent/node-exeunt/blob/master/lib/exeunt.js#L59-L87>.\nThere are some subtleties.\n\nFirst, we can't just exit synchronously:\n\n```javascript\nsetBlocking();\nprocess.exit(code);\n```\n\nbecause that will synchronously call the exit syscall, and the process will\nterminate, before any IO handling to write buffered stdout/stderr. Instead\nwe use `setImmediate` to ensure that there is one more run through the\nnode event loop which [calls\n`uv__io_poll`](https://github.com/nodejs/node/blob/v4.8.0/deps/uv/src/unix/core.c#L354)\nto service IO requests before [calling our `setImmediate`\nhandler](https://github.com/nodejs/node/blob/v4.8.0/deps/uv/src/unix/core.c#L355).\n\nSecond, we said that stdout/stderr will \"most likely be flushed\" above, because\nit appears that [`uv__io_poll` is\ntuned](https://github.com/nodejs/node/blob/v4.8.0/deps/uv/src/unix/kqueue.c#L150)\nto limit the amount of events if will handle in a single event loop pass. I\nhaven't yet come up with example code that hits this threshold, however.\n\n\n## Open Questions\n\nWe haven't verified all our observations yet. This section includes Rumsfeldian\nknown unknowns.\n\n- We need to verify the observations I've made above. At time of writing I was\n  testing out the above examples with node v4.8.0 on macOS 10.11.6.\n\n- What are the conditions in libuv's `uv__io_poll` (which is called once for each\n  pass through the node event loop) such that the `count = 48` guard is\n  triggered, such that not all IO is handled in that last pass?\n    https://github.com/nodejs/node/blob/v4.8.0/deps/uv/src/unix/kqueue.c#L291\n    https://github.com/nodejs/node/blob/v4.8.0/deps/uv/src/unix/sunos.c#L287\n  Understanding this would be useful to know if and what limitation there is\n  on solution 4.\n\n- Test yargs' cases using setBlocking, e.g.\n  <https://github.com/yargs/yargs/blob/8756a3c63dfd2ceae303067b46075de5c982af66/yargs.js#L1010-L1012>\n  to see if they work.\n\n\n## See Also\n\n- [nodejs/node#6980](https://github.com/nodejs/node/issues/6980)\n  \"Tracking issue: stdio problems\".\n  The node.js core issue that aims to be the tracker for issues related to this.\n  Aside: One of the [linked issues](https://github.com/nodejs/node/issues/6456)\n  includes this:\n\n    > If this is currently breaking your program, please use this temporary fix:\n    >\n    >     [process.stdout, process.stderr].forEach((s) => {\n    >       s && s.isTTY && s._handle && s._handle.setBlocking &&\n    >         s._handle.setBlocking(true)\n    >     })\n\n  I believe the `s.isTTY` guard needs to be dropped.\n\n- [nodejs/node@ab3306a](https://github.com/nodejs/node/commit/ab3306ad51d8136014aa0fa9278b57bb77105320)\n  is the commit where a *TTY* is set to blocking. This is why (at least for\n  node releases with this commit), stdout/stderr flushing is not an issue for\n  a node app called interactively and without piping into another program.\n\n- <https://github.com/yargs/set-blocking> is a small module related to the same\n  problem. It states: \"In yargs we only call setBlocking(true) once we already\n  know we are about to call process.exit(code).\"  This is therefore similar\n  to \"Solution 4\" described here, and the provided `exeunt()` function.\n  It isn't clear to me all of yargs' usages of this pattern call `process.exit`\n  in a separate tick, which is necessary to actually flush output.\n\n\n## License\n\nMPL 2.0\n","_attachments":{},"homepage":"https://github.com/joyent/node-exeunt","bugs":{"url":"https://github.com/joyent/node-exeunt/issues"},"license":"MPL-2.0"}