From abe91ca018b4e36223307ac9d07a87674dac76af Mon Sep 17 00:00:00 2001 From: "pat@pulumi.com" Date: Wed, 13 Dec 2017 17:24:47 -0800 Subject: [PATCH 1/2] Treat unhandled promise rejections as uncaught exceptions. Just as uncaught exceptions cause a Pulumi program to exit with a failure code, so should unhandled promise rejections. Fixes pulumi/pulumi-ppc#94. --- sdk/nodejs/cmd/run/index.ts | 8 +++++--- sdk/nodejs/runtime/debuggable.ts | 17 ----------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/sdk/nodejs/cmd/run/index.ts b/sdk/nodejs/cmd/run/index.ts index 53e199dc5..20b93f96b 100644 --- a/sdk/nodejs/cmd/run/index.ts +++ b/sdk/nodejs/cmd/run/index.ts @@ -118,9 +118,9 @@ export function main(args: string[]): void { const programArgs: string[] = argv._.slice(1); process.argv = [ process.argv[0], process.argv[1], ...programArgs ]; - // Set up the process unhandled exception handler and the program exit handler. + // Set up the process uncaught exception, unhandled rejection, and program exit handlers. let uncaught: Error | undefined; - process.on("uncaughtException", (err: Error) => { + const uncaughtHandler = (err: Error) => { // First, log the error. if (err instanceof RunError) { // For errors that are subtypes of RunError, we will print the message without hitting the unhandled error @@ -134,7 +134,9 @@ export function main(args: string[]): void { // Remember that we failed with an error. Don't quit just yet so we have a chance to drain the message loop. uncaught = err; - }); + }; + process.on("uncaughtException", uncaughtHandler); + process.on("unhandledRejection", uncaughtHandler); process.on("exit", (code: number) => { runtime.disconnectSync(); diff --git a/sdk/nodejs/runtime/debuggable.ts b/sdk/nodejs/runtime/debuggable.ts index 8c96931dd..adf2fe02b 100644 --- a/sdk/nodejs/runtime/debuggable.ts +++ b/sdk/nodejs/runtime/debuggable.ts @@ -22,10 +22,6 @@ let leakDetectorScheduled: boolean = false; * leakCandidates tracks the list of potential leak candidates. */ const leakCandidates: Set> = new Set>(); -/** - * unhandledHandlerScheduled is true when the unhandled promise detector is scheduled for this process. - */ -let unhandledHandlerScheduled: boolean = false; function promiseDebugString(p: Promise): string { return `CONTEXT: ${(p)._debugCtx}\n` + @@ -41,19 +37,6 @@ export function debuggablePromise(p: Promise, ctx?: any): Promise { (p)._debugCtx = ctx; (p)._debugStackTrace = new Error().stack; - // If the unhandled handler isn't active yet, schedule it. - if (!unhandledHandlerScheduled) { - process.on("unhandledRejection", (reason, innerPromise) => { - if (!log.hasErrors()) { - console.error("Unhandled promise rejection:"); - console.error(reason); - console.error(reason.stack); - console.error(promiseDebugString(innerPromise)); - } - }); - unhandledHandlerScheduled = true; - } - if (debugPromiseLeaks) { // Setup leak detection. if (!leakDetectorScheduled) { From 4cb7703676d8ae29ac85396c3eff4a12ea74112a Mon Sep 17 00:00:00 2001 From: "pat@pulumi.com" Date: Wed, 13 Dec 2017 17:30:43 -0800 Subject: [PATCH 2/2] Add a test. --- .../runtime/langhost/cases/011.unhandled_error/index.js | 2 +- .../langhost/cases/013.unhandled_promise_rejection/index.js | 3 +++ sdk/nodejs/tests/runtime/langhost/run.spec.ts | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 sdk/nodejs/tests/runtime/langhost/cases/013.unhandled_promise_rejection/index.js diff --git a/sdk/nodejs/tests/runtime/langhost/cases/011.unhandled_error/index.js b/sdk/nodejs/tests/runtime/langhost/cases/011.unhandled_error/index.js index 4d0b8e40d..d4e6b4bab 100644 --- a/sdk/nodejs/tests/runtime/langhost/cases/011.unhandled_error/index.js +++ b/sdk/nodejs/tests/runtime/langhost/cases/011.unhandled_error/index.js @@ -1 +1 @@ -throw new Error("💥 goes the dynamite"); +throw new Error("💥 goes the dynamite"); diff --git a/sdk/nodejs/tests/runtime/langhost/cases/013.unhandled_promise_rejection/index.js b/sdk/nodejs/tests/runtime/langhost/cases/013.unhandled_promise_rejection/index.js new file mode 100644 index 000000000..5b15a4d35 --- /dev/null +++ b/sdk/nodejs/tests/runtime/langhost/cases/013.unhandled_promise_rejection/index.js @@ -0,0 +1,3 @@ +new Promise((resolve, reject) => { + reject(new Error("💥 goes the dynamite (as promised)")); +}); diff --git a/sdk/nodejs/tests/runtime/langhost/run.spec.ts b/sdk/nodejs/tests/runtime/langhost/run.spec.ts index 4bfd848e8..143929ac8 100644 --- a/sdk/nodejs/tests/runtime/langhost/run.spec.ts +++ b/sdk/nodejs/tests/runtime/langhost/run.spec.ts @@ -310,6 +310,12 @@ describe("rpc", () => { return { urn: makeUrn(t, name), id: undefined, props: res }; }, }, + // A program that contains an unhandled promise rejection. + "unhandled_promise_rejection": { + program: path.join(base, "013.unhandled_promise_rejection"), + expectResourceCount: 0, + expectError: "Program exited with non-zero exit code: 1", + }, }; for (const casename of Object.keys(cases)) {