Sema v2.0 Acquire Timeout: Unclear Rejection Timing Across Node LTS Releases
0 reputation · 10 Jun 2022, 15:22 UTC
0 reputation · 10 Jun 2022, 15:22 UTC
The sema library’s acquire() method now supports a timeout option in v2.0, but the exact rejection timing is undocumented for different Node.js LTS releases. When a timeout occurs, the returned promise should reject, yet the delay before rejection appears inconsistent, which can cause false positives or missed alerts in monitoring systems.
Additionally, the API lacks a cancellation mechanism for queued acquire() calls. A long‑running request that never resolves can accumulate unresolved promises, potentially leading to memory leaks and confusing alerting behavior.
To build reliable, noise‑free alerts, we need a clear understanding of timeout semantics and a way to abort pending acquisitions.
\nacquire() timeout rejection timing across Node.js 12, 14, and 16 LTS releases?acquire() call before it resolves?35275 reputation · 10 Jun 2022, 17:16 UTC
The acquire(timeout) promise in sema v2.0 rejects when the internal setTimeout fires, not exactly after the requested milliseconds. Because Node’s timer resolution can shift by a few milliseconds between LTS releases (due to libuv/V8 updates), the observed delay varies but stays within the typical jitter range of 0‑5 ms.
Sema v2.0 implements the timeout by wrapping the acquisition attempt in a Promise that is rejected via a setTimeout callback. The actual moment the callback runs depends on the event‑loop’s timer granularity and any pending work, which has changed slightly across Node 12, 14, and 16 LTS lines.
In Node.js LTS 14, 16, and 18 the minimum timeout for setTimeout is clamped to 1 ms, but the callback may fire later (typically 1‑5 ms) if the event loop is busy. Sema v2.0 rejects the promise precisely when that timer fires.
sema.acquire(5) and record the start time with process.hrtime.bigint().If the measured delays fall within the expected jitter (≈0‑5 ms above the requested timeout), the behavior matches Node’s timer implementation.
Sema v2.0 does not provide a built‑in way to cancel a pending acquire(). To avoid accumulating unresolved promises you can:
Promise.race with your own abort signal (e.g., an AbortController) and ignore the sema result if the abort wins.If you observe jitter larger than the typical 0‑5 ms range, check whether the host has any custom timer adjustments (e.g., --max-old-space-size, UV thread‑pool size, or high external load) that could amplify event‑loop delay. Knowing this determines whether the variance is environmental or requires a code‑level fix.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 10 Jun 2022, 20:23 UTC
In sema v2.0 the acquire(timeout) promise is rejected precisely when the internal setTimeout callback fires in the timer phase of the event loop. This moment is independent of the semaphore’s internal waiting list – if the semaphore becomes free before the timeout, the promise resolves immediately; if it becomes free after, the rejection occurs at the next timer tick after the deadline. Because Node.js 12, 14, and 16 LTS all use the same libuv 1.x series and V8 versions, the timer phase semantics are identical across those releases, so any extra delay you see (typically 0‑2 ms) stems from event‑loop load, not from a version‑specific difference.