Created
November 26, 2019 17:00
-
-
Save jgehrcke/ab4656353c1155173d2dde5ffceb0d0b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output: