From 2aae868fa29469a5424f8286af119cbc8fc57fc1 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 | 19 +++++++++++-------- .../test-timers-immediate-promisified.js | 7 +++++++ .../test-timers-interval-promisified.js | 12 ++++++++++++ .../test-timers-timeout-promisified.js | 7 +++++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/timers/promises.js b/lib/timers/promises.js index 35e52d0f705730..7c8d364b1dde4f 100644 --- a/lib/timers/promises.js +++ b/lib/timers/promises.js @@ -44,7 +44,7 @@ const kScheduler = Symbol('kScheduler'); function cancelListenerHandler(clear, reject, signal) { if (!this._destroyed) { clear(this); - reject(new AbortError()); + reject(new AbortError(undefined, { cause: signal?.reason })); } } @@ -74,7 +74,7 @@ function setTimeout(after, value, options = kEmptyObject) { // 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) => { @@ -82,7 +82,7 @@ function setTimeout(after, value, options = kEmptyObject) { insert(timeout, timeout._idleTimeout); if (signal) { oncancel = FunctionPrototypeBind(cancelListenerHandler, - timeout, clearTimeout, reject); + timeout, clearTimeout, reject, signal); signal.addEventListener('abort', oncancel); } }); @@ -117,7 +117,7 @@ function setImmediate(value, options = kEmptyObject) { // 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) => { @@ -125,7 +125,8 @@ function setImmediate(value, options = kEmptyObject) { if (!ref) immediate.unref(); if (signal) { oncancel = FunctionPrototypeBind(cancelListenerHandler, - immediate, clearImmediate, reject); + immediate, clearImmediate, reject, + signal); signal.addEventListener('abort', oncancel); } }); @@ -142,7 +143,7 @@ async function* setInterval(after, value, options = kEmptyObject) { validateBoolean(ref, 'options.ref'); if (signal?.aborted) - throw new AbortError(); + throw new AbortError(undefined, { cause: signal?.reason }); let onCancel; let interval; @@ -161,7 +162,9 @@ async function* setInterval(after, value, options = kEmptyObject) { onCancel = () => { clearInterval(interval); if (callback) { - callback(PromiseReject(new AbortError())); + callback( + PromiseReject( + new AbortError(undefined, { cause: signal.reason }))); callback = undefined; } }; @@ -176,7 +179,7 @@ async function* setInterval(after, value, options = kEmptyObject) { yield value; } } - throw new AbortError(); + throw new AbortError(undefined, { cause: signal?.reason }); } finally { clearInterval(interval); signal?.removeEventListener('abort', onCancel); 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..e6550445f142eb 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, no-empty + 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());