Skip to content

Instantly share code, notes, and snippets.

@jgehrcke
Created November 26, 2019 17:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgehrcke/ab4656353c1155173d2dde5ffceb0d0b to your computer and use it in GitHub Desktop.
Save jgehrcke/ab4656353c1155173d2dde5ffceb0d0b to your computer and use it in GitHub Desktop.
const child_process = require("child_process");
const winston = require("winston");
const events = require("events");
const logFormat = winston.format.printf(
({ level, message, label, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
}
);
const log = winston.createLogger({
level: "debug",
format: winston.format.combine(
winston.format.splat(),
winston.format.timestamp(),
logFormat
),
transports: [new winston.transports.Console()]
});
function sleep(seconds) {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
async function setupProcess() {
let processStartupError = null;
async function waitUntilError(process) {
log.info("waiting for the startup error to be handled");
while (true) {
if (processStartupError !== null) {
log.error("child process startup error");
// At this point the runtime can reliably know (it could synchronously
// check its internal state) that the process isn't there. It should
// not allow kill(), but instead throw an Error. However,
// `process.kill()` does not throw an error:
log.info("send SIGTERM");
process.kill("SIGTERM");
// Trying again after a significant amount of time, just to make the
// point; this does not throw an error.
await sleep(1.0);
log.info("send SIGTERM");
process.kill("SIGTERM");
// The following is supposed to show that the runtime behaves
// inconsistently: the runtime seems to have internal state that is
// synchronously checked in the code path entered by the following
// await, and based on this state the current execution context is
// terminated magically.
await events.once(process, "close");
// The following log statement and `break` statement are never
// executed. That shows that the runtime knows that the 'close' event
// would never happen. It has already switched out of this context upon
// the `await` above, and detected that it should never come back
// (because it would then wait indefinitely), but that it instead
// should consider this routine to be complete (the runtime terminates
// cleanly with zero exit code in this repro). That is, the runtime
// *knows* that the process is not there. It would only be consistent
// to apply some magic for the kill, too, and let it error out.
log.info("'close' event fired");
break;
}
log.info("loop iteration");
await sleep(0.01);
}
}
const process = child_process.spawn("does-not-exist--");
process.once("error", err => {
// Here, this is supposed to catch ENOENT and EACCESS and the likes.
log.error("'error' handler: %s", err);
processStartupError = err;
});
await waitUntilError(process);
// This is also never emitted.
log.info("waitUntilError completed");
return process;
}
setupProcess();
@jgehrcke
Copy link
Author

Output:

$ node kill_no_pid_repro_js.js 
2019-11-26T16:49:50.509Z info: waiting for the startup error to be handled
2019-11-26T16:49:50.511Z info: loop iteration
2019-11-26T16:49:50.514Z error: 'error' handler: Error: spawn does-not-exist-- ENOENT
    at Process.ChildProcess._handle.onexit (internal/child_process.js:264:19)
    at onErrorNT (internal/child_process.js:456:16)
    at processTicksAndRejections (internal/process/task_queues.js:80:21) {
  errno: 'ENOENT',
  code: 'ENOENT',
  syscall: 'spawn does-not-exist--',
  path: 'does-not-exist--',
  spawnargs: []
}
2019-11-26T16:49:50.521Z error: child process startup error
2019-11-26T16:49:50.521Z info: send SIGTERM
2019-11-26T16:49:51.523Z info: send SIGTERM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment