From e795547651e93b04bde2840e74b6613848095867 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sun, 28 Nov 2021 12:38:42 -0800 Subject: [PATCH] timers: propagate signal.reason in awaitable timers Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/41008 Reviewed-By: Ruben Bridgewater Reviewed-By: Robert Nagy Reviewed-By: Benjamin Gruenbaum --- lib/timers/promises.js | 21 +++++++++++-------- .../test-timers-immediate-promisified.js | 7 +++++++ .../test-timers-interval-promisified.js | 12 +++++++++++ .../test-timers-timeout-promisified.js | 7 +++++++ 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/lib/timers/promises.js b/lib/timers/promises.js index 162f465da29dec..75b7f443401656 100644 --- a/lib/timers/promises.js +++ b/lib/timers/promises.js @@ -24,10 +24,10 @@ const { validateObject, } = require('internal/validators'); -function cancelListenerHandler(clear, reject) { +function cancelListenerHandler(clear, reject, signal) { if (!this._destroyed) { clear(this); - reject(new AbortError()); + reject(new AbortError(undefined, { cause: signal?.reason })); } } @@ -57,7 +57,7 @@ function setTimeout(after, value, options = {}) { // to 12.x, then this can be converted to use optional chaining to // simplify the check. if (signal && signal.aborted) { - return PromiseReject(new AbortError()); + return PromiseReject(new AbortError(undefined, { cause: signal.reason })); } let oncancel; const ret = new Promise((resolve, reject) => { @@ -66,7 +66,7 @@ function setTimeout(after, value, options = {}) { if (signal) { oncancel = FunctionPrototypeBind(cancelListenerHandler, // eslint-disable-next-line no-undef - timeout, clearTimeout, reject); + timeout, clearTimeout, reject, signal); signal.addEventListener('abort', oncancel); } }); @@ -101,7 +101,7 @@ function setImmediate(value, options = {}) { // to 12.x, then this can be converted to use optional chaining to // simplify the check. if (signal && signal.aborted) { - return PromiseReject(new AbortError()); + return PromiseReject(new AbortError(undefined, { cause: signal.reason })); } let oncancel; const ret = new Promise((resolve, reject) => { @@ -110,7 +110,8 @@ function setImmediate(value, options = {}) { if (signal) { oncancel = FunctionPrototypeBind(cancelListenerHandler, // eslint-disable-next-line no-undef - immediate, clearImmediate, reject); + immediate, clearImmediate, reject, + signal); signal.addEventListener('abort', oncancel); } }); @@ -127,7 +128,7 @@ async function* setInterval(after, value, options = {}) { validateBoolean(ref, 'options.ref'); if (signal?.aborted) - throw new AbortError(); + throw new AbortError(undefined, { cause: signal?.reason }); let onCancel; let interval; @@ -147,7 +148,9 @@ async function* setInterval(after, value, options = {}) { // eslint-disable-next-line no-undef clearInterval(interval); if (callback) { - callback(PromiseReject(new AbortError())); + callback( + PromiseReject( + new AbortError(undefined, { cause: signal.reason }))); callback = undefined; } }; @@ -162,7 +165,7 @@ async function* setInterval(after, value, options = {}) { yield value; } } - throw new AbortError(); + throw new AbortError(undefined, { cause: signal?.reason }); } finally { // eslint-disable-next-line no-undef clearInterval(interval); diff --git a/test/parallel/test-timers-immediate-promisified.js b/test/parallel/test-timers-immediate-promisified.js index 65c8411f1b2ffd..5808312b564e48 100644 --- a/test/parallel/test-timers-immediate-promisified.js +++ b/test/parallel/test-timers-immediate-promisified.js @@ -97,3 +97,10 @@ process.on('multipleResolves', common.mustNotCall()); assert.strictEqual(stderr, ''); })); } + +(async () => { + const signal = AbortSignal.abort('boom'); + await assert.rejects(timerPromises.setImmediate(undefined, { signal }), { + cause: 'boom', + }); +})().then(common.mustCall()); diff --git a/test/parallel/test-timers-interval-promisified.js b/test/parallel/test-timers-interval-promisified.js index 28a0d0b47cd182..9c11a9d9870fa7 100644 --- a/test/parallel/test-timers-interval-promisified.js +++ b/test/parallel/test-timers-interval-promisified.js @@ -246,3 +246,15 @@ process.on('multipleResolves', common.mustNotCall()); setPromiseTimeout(time_unit * 3).then(() => post = true), ]).then(common.mustCall()); } + +(async () => { + const signal = AbortSignal.abort('boom'); + try { + const iterable = timerPromises.setInterval(2, undefined, { signal }); + // eslint-disable-next-line no-unused-vars + for await (const _ of iterable) {} + assert.fail('should have failed'); + } catch (err) { + assert.strictEqual(err.cause, 'boom'); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-timers-timeout-promisified.js b/test/parallel/test-timers-timeout-promisified.js index 0b9a6b6f19a1c2..4e3881acec1ce2 100644 --- a/test/parallel/test-timers-timeout-promisified.js +++ b/test/parallel/test-timers-timeout-promisified.js @@ -97,3 +97,10 @@ process.on('multipleResolves', common.mustNotCall()); assert.strictEqual(stderr, ''); })); } + +(async () => { + const signal = AbortSignal.abort('boom'); + await assert.rejects(timerPromises.setTimeout(1, undefined, { signal }), { + cause: 'boom', + }); +})().then(common.mustCall());