No Abort in PostCSS 8: Who Owns the Timeout for a Hung Async Plugin?
0 reputation · 24 Nov 2021, 05:41 UTC
0 reputation · 24 Nov 2021, 05:41 UTC
On PostCSS 8, a plugin may return a Promise from its root handler or visitor hooks, and process() returns a LazyResult that settles only when every plugin finishes. The documented model covers completion and error propagation: it defines no abort signal, cancellation token, or per-plugin timeout.
That leaves an open design question for anyone embedding PostCSS under a latency budget. A plugin awaiting an external resource can hold the pipeline open indefinitely. A caller can race the LazyResult against an external deadline, but rejecting that race only abandons the caller's await; the plugin's pending work continues, and nothing in the API says what it may still touch. Deferred execution complicates this further: plugin work starts when .css, .root, .then(), or .catch() is first accessed, so a deadline measured from the process() call differs from one measured at actual start.
The unresolved decision is where responsibility for bounding plugin time belongs:
process() is invoked, or is any sanctioned interruption path planned?LazyResult, what is the expected state of the plugin's pending promises and the eventual Result?35275 reputation · 24 Nov 2021, 16:56 UTC
PostCSS 8’s process() returns a LazyResult that settles only when every plugin’s returned promise (if any) resolves or rejects. The API defines no abort signal, cancellation token, or per‑plugin timeout mechanism. Therefore any time‑bounding must be performed by the caller/integrator.
process() → LazyResult (no AbortSignal or timeout options)..css, .root, .then(), or .catch().LazyResult (e.g., via Promise.race with a timer) does not cancel the plugin’s internal promise.LazyResult (or its .then()) against that timeout:const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('PostCSS timeout')), 200));
postcss([myPlugin])
.process(css)
.then(result => {/* normal path */})
.catch(err => {/* timeout or plugin error */});
Promise.race([postcssPromise, timeout]).then(...).catch(...);
LazyResult is rejected, but the plugin’s pending promise remains untouched; nothing in PostCSS guarantees that the plugin will stop or clean up.AbortSignal or a similar cancellation mechanism, pass that signal to the plugin and rely on its internal abort logic. Otherwise, accept that the plugin’s side‑effects may continue after the caller’s timeout.Does the specific plugin you are using expose a documented way to abort its asynchronous work (e.g., via an AbortSignal option)? If yes, use that mechanism; if no, the external race shown above is the only safe approach, with the caveat that the plugin may still run to completion.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.