async-bulkhead-ts 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,6 +88,10 @@ const bulkhead = createBulkhead({
88
88
  });
89
89
  ```
90
90
 
91
+ > Note: bounded waiting is optional.
92
+ > Future major versions may focus on fail-fast admission only.
93
+
94
+
91
95
  Semantics:
92
96
  - If `inFlight` < `maxConcurrent`: `acquire()` succeeds immediately.
93
97
  - Else if `maxQueue` > 0 and queue has space: `acquire()` waits FIFO.
@@ -108,6 +112,9 @@ Cancellation guarantees:
108
112
  - Work that is waiting in the queue can be cancelled before it starts.
109
113
  - In-flight work is not forcibly terminated (your function may observe the signal).
110
114
  - Capacity is always released correctly for acquired tokens.
115
+ - Cancelled or timed-out waiters do not permanently consume queue capacity.
116
+ - Cancelled waiters will not block subsequent admissions.
117
+ - FIFO order is preserved for non-cancelled waiters.
111
118
 
112
119
  You can also bound waiting time:
113
120
 
@@ -115,6 +122,12 @@ You can also bound waiting time:
115
122
  await bulkhead.run(async () => doWork(), { timeoutMs: 50 });
116
123
  ```
117
124
 
125
+ ## Behavioral Guarantees
126
+
127
+ - maxConcurrent is never exceeded.
128
+ - pending never exceeds maxQueue.
129
+ - Under cancellation or timeout churn, admission remains bounded and deterministic.
130
+
118
131
  ## API
119
132
 
120
133
  `createBulkhead(options)`
package/dist/index.cjs CHANGED
@@ -24,6 +24,47 @@ __export(index_exports, {
24
24
  createBulkhead: () => createBulkhead
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
+ var RingDeque = class {
28
+ buf;
29
+ head = 0;
30
+ tail = 0;
31
+ size = 0;
32
+ constructor(capacity) {
33
+ const cap = Math.max(4, capacity | 0);
34
+ this.buf = new Array(cap);
35
+ }
36
+ get length() {
37
+ return this.size;
38
+ }
39
+ pushBack(item) {
40
+ if (this.size === this.buf.length) {
41
+ this.grow();
42
+ }
43
+ const idx = (this.head + this.size) % this.buf.length;
44
+ this.buf[idx] = item;
45
+ this.size++;
46
+ }
47
+ peekFront() {
48
+ if (this.size === 0) return void 0;
49
+ return this.buf[this.head];
50
+ }
51
+ popFront() {
52
+ if (this.size === 0) return void 0;
53
+ const item = this.buf[this.head];
54
+ this.buf[this.head] = void 0;
55
+ this.head = (this.head + 1) % this.buf.length;
56
+ this.size--;
57
+ return item;
58
+ }
59
+ grow() {
60
+ const newBuf = new Array(this.buf.length * 2);
61
+ for (let i = 0; i < this.size; i++) {
62
+ newBuf[i] = this.buf[(this.head + i) % this.buf.length];
63
+ }
64
+ this.buf = newBuf;
65
+ this.head = 0;
66
+ }
67
+ };
27
68
  var BulkheadRejectedError = class extends Error {
28
69
  constructor(reason) {
29
70
  super(`Bulkhead rejected: ${reason}`);
@@ -41,31 +82,11 @@ function createBulkhead(opts) {
41
82
  throw new Error("maxQueue must be an integer >= 0");
42
83
  }
43
84
  let inFlight = 0;
44
- const q = [];
45
- let qHead = 0;
85
+ const q = new RingDeque(maxQueue + 1);
46
86
  let aborted = 0;
47
87
  let timedOut = 0;
48
88
  let rejected = 0;
49
89
  let doubleRelease = 0;
50
- const pruneHead = () => {
51
- while (qHead < q.length) {
52
- const w = q[qHead];
53
- if (w.cancelled || w.settled) {
54
- cleanupWaiter(w);
55
- qHead++;
56
- continue;
57
- }
58
- break;
59
- }
60
- if (qHead > 1024 && qHead * 2 > q.length) {
61
- q.splice(0, qHead);
62
- qHead = 0;
63
- }
64
- };
65
- const pendingCount = () => {
66
- pruneHead();
67
- return q.length - qHead;
68
- };
69
90
  const makeToken = () => {
70
91
  let released = false;
71
92
  return {
@@ -81,6 +102,22 @@ function createBulkhead(opts) {
81
102
  }
82
103
  };
83
104
  };
105
+ const pruneCancelledFront = () => {
106
+ while (q.length > 0) {
107
+ const w = q.peekFront();
108
+ if (w.cancelled || w.settled) {
109
+ cleanupWaiter(w);
110
+ q.popFront();
111
+ continue;
112
+ } else {
113
+ break;
114
+ }
115
+ }
116
+ };
117
+ const pendingCount = () => {
118
+ pruneCancelledFront();
119
+ return q.length;
120
+ };
84
121
  const cleanupWaiter = (w) => {
85
122
  if (w.abortListener) w.abortListener();
86
123
  if (w.timeoutId) clearTimeout(w.timeoutId);
@@ -95,11 +132,12 @@ function createBulkhead(opts) {
95
132
  w.resolve(r);
96
133
  };
97
134
  const drain = () => {
98
- pruneHead();
99
- while (inFlight < opts.maxConcurrent && pendingCount() > 0) {
100
- const w = q[qHead++];
101
- if (w.cancelled || w.settled) {
135
+ pruneCancelledFront();
136
+ while (inFlight < opts.maxConcurrent && q.length > 0) {
137
+ const w = q.popFront();
138
+ if (w.cancelled) {
102
139
  cleanupWaiter(w);
140
+ pruneCancelledFront();
103
141
  continue;
104
142
  }
105
143
  inFlight++;
@@ -160,7 +198,7 @@ function createBulkhead(opts) {
160
198
  settle(w, { ok: false, reason: "timeout" });
161
199
  }, ao.timeoutMs);
162
200
  }
163
- q.push(w);
201
+ q.pushBack(w);
164
202
  drain();
165
203
  });
166
204
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type BulkheadOptions = {\n maxConcurrent: number;\n maxQueue?: number; // pending waiters allowed (0 => no waiting)\n};\n\nexport type AcquireOptions = {\n signal?: AbortSignal;\n timeoutMs?: number; // waiting timeout only\n};\n\nexport type Stats = {\n inFlight: number;\n pending: number;\n maxConcurrent: number;\n maxQueue: number;\n // optional debug counters:\n aborted?: number;\n timedOut?: number;\n rejected?: number;\n doubleRelease?: number;\n};\n\nexport type Token = { release(): void };\n\nexport type TryAcquireResult =\n | { ok: true; token: Token }\n | { ok: false; reason: 'concurrency_limit' };\n\nexport type AcquireResult =\n | { ok: true; token: Token }\n | { ok: false; reason: 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted' };\n\ntype Waiter = {\n resolve: (r: AcquireResult) => void;\n cancelled: boolean;\n settled: boolean;\n\n abortListener: (() => void) | undefined;\n timeoutId: ReturnType<typeof setTimeout> | undefined;\n};\n\nexport type RejectReason = 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted';\n\nexport class BulkheadRejectedError extends Error {\n readonly code = 'BULKHEAD_REJECTED' as const;\n\n constructor(readonly reason: RejectReason) {\n super(`Bulkhead rejected: ${reason}`);\n this.name = 'BulkheadRejectedError';\n }\n}\n\nexport function createBulkhead(opts: BulkheadOptions) {\n // ---- validate ----\n if (!Number.isInteger(opts.maxConcurrent) || opts.maxConcurrent <= 0) {\n throw new Error('maxConcurrent must be a positive integer');\n }\n const maxQueue = opts.maxQueue ?? 0;\n if (!Number.isInteger(maxQueue) || maxQueue < 0) {\n throw new Error('maxQueue must be an integer >= 0');\n }\n\n // ---- state ----\n let inFlight = 0;\n\n // FIFO queue as array with head index (cheap shift)\n const q: Waiter[] = [];\n let qHead = 0;\n\n // optional counters\n let aborted = 0;\n let timedOut = 0;\n let rejected = 0;\n let doubleRelease = 0;\n\n const pruneHead = () => {\n // Advance past already-cancelled/settled waiters so they don't count against maxQueue.\n while (qHead < q.length) {\n const w = q[qHead]!;\n if (w.cancelled || w.settled) {\n cleanupWaiter(w);\n qHead++;\n continue;\n }\n break;\n }\n\n // occasional compaction to avoid unbounded growth\n if (qHead > 1024 && qHead * 2 > q.length) {\n q.splice(0, qHead);\n qHead = 0;\n }\n };\n\n const pendingCount = () => {\n pruneHead();\n return q.length - qHead;\n };\n\n // ---- token factory ----\n const makeToken = (): Token => {\n let released = false;\n return {\n release() {\n if (released) {\n doubleRelease++;\n return; // idempotent; consider throw in dev builds if you prefer\n }\n released = true;\n inFlight--;\n if (inFlight < 0) inFlight = 0; // defensive, should never happen\n drain();\n },\n };\n };\n\n const cleanupWaiter = (w: Waiter) => {\n if (w.abortListener) w.abortListener();\n if (w.timeoutId) clearTimeout(w.timeoutId);\n w.abortListener = undefined;\n w.timeoutId = undefined;\n };\n\n const settle = (w: Waiter, r: AcquireResult) => {\n if (w.settled) return;\n w.settled = true;\n // Once settled, it's effectively cancelled for drain-skipping purposes.\n // (We keep cancelled separate because drain checks it.)\n if (!w.cancelled && !r.ok) w.cancelled = true;\n cleanupWaiter(w);\n w.resolve(r);\n };\n\n // ---- drain algorithm ----\n const drain = () => {\n // Important: prune even when there's no capacity, so cancelled/settled\n // waiters stop consuming maxQueue immediately.\n pruneHead();\n\n while (inFlight < opts.maxConcurrent && pendingCount() > 0) {\n const w = q[qHead++]!;\n // skip cancelled waiters\n if (w.cancelled || w.settled) {\n cleanupWaiter(w);\n continue;\n }\n\n // grant slot\n inFlight++;\n settle(w, { ok: true, token: makeToken() });\n }\n };\n\n // ---- public APIs ----\n\n const tryAcquire = (): TryAcquireResult => {\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return { ok: true, token: makeToken() };\n }\n return { ok: false, reason: 'concurrency_limit' };\n };\n\n const acquire = (ao: AcquireOptions = {}): Promise<AcquireResult> => {\n // immediate fast path\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return Promise.resolve({ ok: true, token: makeToken() });\n }\n\n // no waiting allowed\n if (maxQueue === 0) {\n rejected++;\n return Promise.resolve({ ok: false, reason: 'concurrency_limit' });\n }\n\n // bounded waiting\n if (pendingCount() >= maxQueue) {\n rejected++;\n return Promise.resolve({ ok: false, reason: 'queue_limit' });\n }\n\n // enqueue\n return new Promise<AcquireResult>((resolve) => {\n const w: Waiter = {\n resolve,\n cancelled: false,\n settled: false,\n abortListener: undefined,\n timeoutId: undefined,\n };\n\n // abort support\n if (ao.signal) {\n if (ao.signal.aborted) {\n aborted++;\n settle(w, { ok: false, reason: 'aborted' });\n return;\n }\n const onAbort = () => {\n // mark cancelled; drain() will skip it\n aborted++;\n w.cancelled = true;\n settle(w, { ok: false, reason: 'aborted' });\n };\n\n ao.signal.addEventListener('abort', onAbort, { once: true });\n w.abortListener = () => ao.signal!.removeEventListener('abort', onAbort);\n }\n\n // timeout support (waiting only)\n if (ao.timeoutMs != null) {\n if (!Number.isFinite(ao.timeoutMs) || ao.timeoutMs < 0) {\n // invalid => treat as immediate timeout\n timedOut++;\n settle(w, { ok: false, reason: 'timeout' });\n return;\n }\n w.timeoutId = setTimeout(() => {\n timedOut++;\n w.cancelled = true;\n settle(w, { ok: false, reason: 'timeout' });\n }, ao.timeoutMs);\n }\n\n q.push(w);\n drain(); // required: capacity may have freed after the fast-path check but before enqueue\n });\n };\n\n const run = async <T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n ao: AcquireOptions = {},\n ): Promise<T> => {\n const r = await acquire(ao);\n if (!r.ok) {\n throw new BulkheadRejectedError(r.reason);\n }\n try {\n return await fn(ao.signal);\n } finally {\n r.token.release();\n }\n };\n\n const stats = (): Stats => ({\n inFlight,\n pending: pendingCount(),\n maxConcurrent: opts.maxConcurrent,\n maxQueue,\n aborted,\n timedOut,\n rejected,\n doubleRelease,\n });\n\n return { tryAcquire, acquire, run, stats };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * A small ring-buffer queue.\n * - O(1) pushBack / popFront\n * - avoids array shift and head-index + compaction heuristics \n */\nclass RingDeque<T> {\n private buf: Array<T | undefined>;\n private head = 0;\n private tail = 0;\n private size = 0;\n\n constructor(capacity: number) {\n const cap = Math.max(4, capacity | 0);\n this.buf = new Array<T | undefined>(cap);\n }\n\n get length() {\n return this.size;\n }\n\n pushBack(item: T) {\n if (this.size === this.buf.length) {\n this.grow();\n }\n const idx = (this.head + this.size) % this.buf.length;\n this.buf[idx] = item;\n this.size++;\n }\n\n peekFront(): T | undefined {\n if (this.size === 0) return undefined;\n return this.buf[this.head];\n }\n\n popFront(): T | undefined {\n if (this.size === 0) return undefined;\n const item = this.buf[this.head];\n this.buf[this.head] = undefined; // help GC\n this.head = (this.head + 1) % this.buf.length;\n this.size--;\n return item;\n }\n\n private grow() {\n const newBuf = new Array<T | undefined>(this.buf.length * 2);\n for (let i = 0; i < this.size; i++) {\n newBuf[i] = this.buf[(this.head + i) % this.buf.length];\n }\n this.buf = newBuf;\n this.head = 0;\n }\n}\n\nexport type BulkheadOptions = {\n maxConcurrent: number;\n maxQueue?: number; // pending waiters allowed (0 => no waiting)\n};\n\nexport type AcquireOptions = {\n signal?: AbortSignal;\n timeoutMs?: number; // waiting timeout only\n};\n\nexport type Stats = {\n inFlight: number;\n pending: number;\n maxConcurrent: number;\n maxQueue: number;\n // optional debug counters:\n aborted?: number;\n timedOut?: number;\n rejected?: number;\n doubleRelease?: number;\n};\n\nexport type Token = { release(): void };\n\nexport type TryAcquireResult =\n | { ok: true; token: Token }\n | { ok: false; reason: 'concurrency_limit' };\n\nexport type AcquireResult =\n | { ok: true; token: Token }\n | { ok: false; reason: 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted' };\n\ntype Waiter = {\n resolve: (r: AcquireResult) => void;\n cancelled: boolean;\n settled: boolean;\n\n abortListener: (() => void) | undefined;\n timeoutId: ReturnType<typeof setTimeout> | undefined;\n};\n\nexport type RejectReason = 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted';\n\nexport class BulkheadRejectedError extends Error {\n readonly code = 'BULKHEAD_REJECTED' as const;\n\n constructor(readonly reason: RejectReason) {\n super(`Bulkhead rejected: ${reason}`);\n this.name = 'BulkheadRejectedError';\n }\n}\n\nexport function createBulkhead(opts: BulkheadOptions) {\n // ---- validate ----\n if (!Number.isInteger(opts.maxConcurrent) || opts.maxConcurrent <= 0) {\n throw new Error('maxConcurrent must be a positive integer');\n }\n const maxQueue = opts.maxQueue ?? 0;\n if (!Number.isInteger(maxQueue) || maxQueue < 0) {\n throw new Error('maxQueue must be an integer >= 0');\n }\n\n // ---- state ----\n let inFlight = 0;\n\n // FIFO queue as deque (no head index, just pushBack / popFront)\n const q = new RingDeque<Waiter>(maxQueue + 1); // +1 to avoid full queue edge case\n\n // optional counters\n let aborted = 0;\n let timedOut = 0;\n let rejected = 0;\n let doubleRelease = 0;\n\n // ---- token factory ----\n const makeToken = (): Token => {\n let released = false;\n return {\n release() {\n if (released) {\n doubleRelease++;\n return; // idempotent; consider throw in dev builds if you prefer\n }\n released = true;\n inFlight--;\n if (inFlight < 0) inFlight = 0; // defensive, should never happen\n drain();\n },\n };\n };\n\n const pruneCancelledFront = () => {\n // Remove cancelled/settled waiters at the front so they stop consuming maxQueue.\n while (q.length > 0) {\n const w = q.peekFront()!;\n if (w.cancelled || w.settled) {\n cleanupWaiter(w);\n q.popFront();\n continue;\n } else {\n break;\n }\n }\n }\n\n const pendingCount = () => {\n pruneCancelledFront();\n return q.length;\n }\n\n const cleanupWaiter = (w: Waiter) => {\n if (w.abortListener) w.abortListener();\n if (w.timeoutId) clearTimeout(w.timeoutId);\n w.abortListener = undefined;\n w.timeoutId = undefined;\n };\n\n const settle = (w: Waiter, r: AcquireResult) => {\n if (w.settled) return;\n w.settled = true;\n // Once settled, it's effectively cancelled for drain-skipping purposes.\n // (We keep cancelled separate because drain checks it.)\n if (!w.cancelled && !r.ok) w.cancelled = true;\n cleanupWaiter(w);\n w.resolve(r);\n };\n\n // ---- drain algorithm ----\n const drain = () => {\n // Prune first so cancelled/settled waiters don't block the head.\n pruneCancelledFront();\n while (inFlight < opts.maxConcurrent && q.length > 0) {\n const w = q.popFront()!; \n // If it was cancelled after peek/prune but before pop, skip it.\n if (w.cancelled) {\n cleanupWaiter(w);\n pruneCancelledFront(); // in case there are more cancelled after this one\n continue;\n }\n inFlight++;\n settle(w, { ok: true, token: makeToken() });\n }\n };\n\n // ---- public APIs ----\n\n const tryAcquire = (): TryAcquireResult => {\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return { ok: true, token: makeToken() };\n }\n return { ok: false, reason: 'concurrency_limit' };\n };\n\n const acquire = (ao: AcquireOptions = {}): Promise<AcquireResult> => {\n // immediate fast path\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return Promise.resolve({ ok: true, token: makeToken() });\n }\n\n // no waiting allowed\n if (maxQueue === 0) {\n rejected++;\n return Promise.resolve({ ok: false, reason: 'concurrency_limit' });\n }\n\n // bounded waiting\n if (pendingCount() >= maxQueue) {\n rejected++;\n return Promise.resolve({ ok: false, reason: 'queue_limit' });\n }\n\n // enqueue\n return new Promise<AcquireResult>((resolve) => {\n const w: Waiter = {\n resolve,\n cancelled: false,\n settled: false,\n abortListener: undefined,\n timeoutId: undefined,\n };\n\n // abort support\n if (ao.signal) {\n if (ao.signal.aborted) {\n aborted++;\n settle(w, { ok: false, reason: 'aborted' });\n return;\n }\n const onAbort = () => {\n // mark cancelled; drain() will skip it\n aborted++;\n w.cancelled = true;\n settle(w, { ok: false, reason: 'aborted' });\n };\n\n ao.signal.addEventListener('abort', onAbort, { once: true });\n w.abortListener = () => ao.signal!.removeEventListener('abort', onAbort);\n }\n\n // timeout support (waiting only)\n if (ao.timeoutMs != null) {\n if (!Number.isFinite(ao.timeoutMs) || ao.timeoutMs < 0) {\n // invalid => treat as immediate timeout\n timedOut++;\n settle(w, { ok: false, reason: 'timeout' });\n return;\n }\n w.timeoutId = setTimeout(() => {\n timedOut++;\n w.cancelled = true;\n settle(w, { ok: false, reason: 'timeout' });\n }, ao.timeoutMs);\n }\n\n q.pushBack(w);\n drain(); // required: capacity may have freed after the fast-path check but before enqueue\n });\n };\n\n const run = async <T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n ao: AcquireOptions = {},\n ): Promise<T> => {\n const r = await acquire(ao);\n if (!r.ok) {\n throw new BulkheadRejectedError(r.reason);\n }\n try {\n return await fn(ao.signal);\n } finally {\n r.token.release();\n }\n };\n\n const stats = (): Stats => ({\n inFlight,\n pending: pendingCount(),\n maxConcurrent: opts.maxConcurrent,\n maxQueue,\n aborted,\n timedOut,\n rejected,\n doubleRelease,\n });\n\n return { tryAcquire, acquire, run, stats };\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1,4 +1,45 @@
1
1
  // src/index.ts
2
+ var RingDeque = class {
3
+ buf;
4
+ head = 0;
5
+ tail = 0;
6
+ size = 0;
7
+ constructor(capacity) {
8
+ const cap = Math.max(4, capacity | 0);
9
+ this.buf = new Array(cap);
10
+ }
11
+ get length() {
12
+ return this.size;
13
+ }
14
+ pushBack(item) {
15
+ if (this.size === this.buf.length) {
16
+ this.grow();
17
+ }
18
+ const idx = (this.head + this.size) % this.buf.length;
19
+ this.buf[idx] = item;
20
+ this.size++;
21
+ }
22
+ peekFront() {
23
+ if (this.size === 0) return void 0;
24
+ return this.buf[this.head];
25
+ }
26
+ popFront() {
27
+ if (this.size === 0) return void 0;
28
+ const item = this.buf[this.head];
29
+ this.buf[this.head] = void 0;
30
+ this.head = (this.head + 1) % this.buf.length;
31
+ this.size--;
32
+ return item;
33
+ }
34
+ grow() {
35
+ const newBuf = new Array(this.buf.length * 2);
36
+ for (let i = 0; i < this.size; i++) {
37
+ newBuf[i] = this.buf[(this.head + i) % this.buf.length];
38
+ }
39
+ this.buf = newBuf;
40
+ this.head = 0;
41
+ }
42
+ };
2
43
  var BulkheadRejectedError = class extends Error {
3
44
  constructor(reason) {
4
45
  super(`Bulkhead rejected: ${reason}`);
@@ -16,31 +57,11 @@ function createBulkhead(opts) {
16
57
  throw new Error("maxQueue must be an integer >= 0");
17
58
  }
18
59
  let inFlight = 0;
19
- const q = [];
20
- let qHead = 0;
60
+ const q = new RingDeque(maxQueue + 1);
21
61
  let aborted = 0;
22
62
  let timedOut = 0;
23
63
  let rejected = 0;
24
64
  let doubleRelease = 0;
25
- const pruneHead = () => {
26
- while (qHead < q.length) {
27
- const w = q[qHead];
28
- if (w.cancelled || w.settled) {
29
- cleanupWaiter(w);
30
- qHead++;
31
- continue;
32
- }
33
- break;
34
- }
35
- if (qHead > 1024 && qHead * 2 > q.length) {
36
- q.splice(0, qHead);
37
- qHead = 0;
38
- }
39
- };
40
- const pendingCount = () => {
41
- pruneHead();
42
- return q.length - qHead;
43
- };
44
65
  const makeToken = () => {
45
66
  let released = false;
46
67
  return {
@@ -56,6 +77,22 @@ function createBulkhead(opts) {
56
77
  }
57
78
  };
58
79
  };
80
+ const pruneCancelledFront = () => {
81
+ while (q.length > 0) {
82
+ const w = q.peekFront();
83
+ if (w.cancelled || w.settled) {
84
+ cleanupWaiter(w);
85
+ q.popFront();
86
+ continue;
87
+ } else {
88
+ break;
89
+ }
90
+ }
91
+ };
92
+ const pendingCount = () => {
93
+ pruneCancelledFront();
94
+ return q.length;
95
+ };
59
96
  const cleanupWaiter = (w) => {
60
97
  if (w.abortListener) w.abortListener();
61
98
  if (w.timeoutId) clearTimeout(w.timeoutId);
@@ -70,11 +107,12 @@ function createBulkhead(opts) {
70
107
  w.resolve(r);
71
108
  };
72
109
  const drain = () => {
73
- pruneHead();
74
- while (inFlight < opts.maxConcurrent && pendingCount() > 0) {
75
- const w = q[qHead++];
76
- if (w.cancelled || w.settled) {
110
+ pruneCancelledFront();
111
+ while (inFlight < opts.maxConcurrent && q.length > 0) {
112
+ const w = q.popFront();
113
+ if (w.cancelled) {
77
114
  cleanupWaiter(w);
115
+ pruneCancelledFront();
78
116
  continue;
79
117
  }
80
118
  inFlight++;
@@ -135,7 +173,7 @@ function createBulkhead(opts) {
135
173
  settle(w, { ok: false, reason: "timeout" });
136
174
  }, ao.timeoutMs);
137
175
  }
138
- q.push(w);
176
+ q.pushBack(w);
139
177
  drain();
140
178
  });
141
179
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type BulkheadOptions = {\n maxConcurrent: number;\n maxQueue?: number; // pending waiters allowed (0 => no waiting)\n};\n\nexport type AcquireOptions = {\n signal?: AbortSignal;\n timeoutMs?: number; // waiting timeout only\n};\n\nexport type Stats = {\n inFlight: number;\n pending: number;\n maxConcurrent: number;\n maxQueue: number;\n // optional debug counters:\n aborted?: number;\n timedOut?: number;\n rejected?: number;\n doubleRelease?: number;\n};\n\nexport type Token = { release(): void };\n\nexport type TryAcquireResult =\n | { ok: true; token: Token }\n | { ok: false; reason: 'concurrency_limit' };\n\nexport type AcquireResult =\n | { ok: true; token: Token }\n | { ok: false; reason: 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted' };\n\ntype Waiter = {\n resolve: (r: AcquireResult) => void;\n cancelled: boolean;\n settled: boolean;\n\n abortListener: (() => void) | undefined;\n timeoutId: ReturnType<typeof setTimeout> | undefined;\n};\n\nexport type RejectReason = 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted';\n\nexport class BulkheadRejectedError extends Error {\n readonly code = 'BULKHEAD_REJECTED' as const;\n\n constructor(readonly reason: RejectReason) {\n super(`Bulkhead rejected: ${reason}`);\n this.name = 'BulkheadRejectedError';\n }\n}\n\nexport function createBulkhead(opts: BulkheadOptions) {\n // ---- validate ----\n if (!Number.isInteger(opts.maxConcurrent) || opts.maxConcurrent <= 0) {\n throw new Error('maxConcurrent must be a positive integer');\n }\n const maxQueue = opts.maxQueue ?? 0;\n if (!Number.isInteger(maxQueue) || maxQueue < 0) {\n throw new Error('maxQueue must be an integer >= 0');\n }\n\n // ---- state ----\n let inFlight = 0;\n\n // FIFO queue as array with head index (cheap shift)\n const q: Waiter[] = [];\n let qHead = 0;\n\n // optional counters\n let aborted = 0;\n let timedOut = 0;\n let rejected = 0;\n let doubleRelease = 0;\n\n const pruneHead = () => {\n // Advance past already-cancelled/settled waiters so they don't count against maxQueue.\n while (qHead < q.length) {\n const w = q[qHead]!;\n if (w.cancelled || w.settled) {\n cleanupWaiter(w);\n qHead++;\n continue;\n }\n break;\n }\n\n // occasional compaction to avoid unbounded growth\n if (qHead > 1024 && qHead * 2 > q.length) {\n q.splice(0, qHead);\n qHead = 0;\n }\n };\n\n const pendingCount = () => {\n pruneHead();\n return q.length - qHead;\n };\n\n // ---- token factory ----\n const makeToken = (): Token => {\n let released = false;\n return {\n release() {\n if (released) {\n doubleRelease++;\n return; // idempotent; consider throw in dev builds if you prefer\n }\n released = true;\n inFlight--;\n if (inFlight < 0) inFlight = 0; // defensive, should never happen\n drain();\n },\n };\n };\n\n const cleanupWaiter = (w: Waiter) => {\n if (w.abortListener) w.abortListener();\n if (w.timeoutId) clearTimeout(w.timeoutId);\n w.abortListener = undefined;\n w.timeoutId = undefined;\n };\n\n const settle = (w: Waiter, r: AcquireResult) => {\n if (w.settled) return;\n w.settled = true;\n // Once settled, it's effectively cancelled for drain-skipping purposes.\n // (We keep cancelled separate because drain checks it.)\n if (!w.cancelled && !r.ok) w.cancelled = true;\n cleanupWaiter(w);\n w.resolve(r);\n };\n\n // ---- drain algorithm ----\n const drain = () => {\n // Important: prune even when there's no capacity, so cancelled/settled\n // waiters stop consuming maxQueue immediately.\n pruneHead();\n\n while (inFlight < opts.maxConcurrent && pendingCount() > 0) {\n const w = q[qHead++]!;\n // skip cancelled waiters\n if (w.cancelled || w.settled) {\n cleanupWaiter(w);\n continue;\n }\n\n // grant slot\n inFlight++;\n settle(w, { ok: true, token: makeToken() });\n }\n };\n\n // ---- public APIs ----\n\n const tryAcquire = (): TryAcquireResult => {\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return { ok: true, token: makeToken() };\n }\n return { ok: false, reason: 'concurrency_limit' };\n };\n\n const acquire = (ao: AcquireOptions = {}): Promise<AcquireResult> => {\n // immediate fast path\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return Promise.resolve({ ok: true, token: makeToken() });\n }\n\n // no waiting allowed\n if (maxQueue === 0) {\n rejected++;\n return Promise.resolve({ ok: false, reason: 'concurrency_limit' });\n }\n\n // bounded waiting\n if (pendingCount() >= maxQueue) {\n rejected++;\n return Promise.resolve({ ok: false, reason: 'queue_limit' });\n }\n\n // enqueue\n return new Promise<AcquireResult>((resolve) => {\n const w: Waiter = {\n resolve,\n cancelled: false,\n settled: false,\n abortListener: undefined,\n timeoutId: undefined,\n };\n\n // abort support\n if (ao.signal) {\n if (ao.signal.aborted) {\n aborted++;\n settle(w, { ok: false, reason: 'aborted' });\n return;\n }\n const onAbort = () => {\n // mark cancelled; drain() will skip it\n aborted++;\n w.cancelled = true;\n settle(w, { ok: false, reason: 'aborted' });\n };\n\n ao.signal.addEventListener('abort', onAbort, { once: true });\n w.abortListener = () => ao.signal!.removeEventListener('abort', onAbort);\n }\n\n // timeout support (waiting only)\n if (ao.timeoutMs != null) {\n if (!Number.isFinite(ao.timeoutMs) || ao.timeoutMs < 0) {\n // invalid => treat as immediate timeout\n timedOut++;\n settle(w, { ok: false, reason: 'timeout' });\n return;\n }\n w.timeoutId = setTimeout(() => {\n timedOut++;\n w.cancelled = true;\n settle(w, { ok: false, reason: 'timeout' });\n }, ao.timeoutMs);\n }\n\n q.push(w);\n drain(); // required: capacity may have freed after the fast-path check but before enqueue\n });\n };\n\n const run = async <T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n ao: AcquireOptions = {},\n ): Promise<T> => {\n const r = await acquire(ao);\n if (!r.ok) {\n throw new BulkheadRejectedError(r.reason);\n }\n try {\n return await fn(ao.signal);\n } finally {\n r.token.release();\n }\n };\n\n const stats = (): Stats => ({\n inFlight,\n pending: pendingCount(),\n maxConcurrent: opts.maxConcurrent,\n maxQueue,\n aborted,\n timedOut,\n rejected,\n doubleRelease,\n });\n\n return { tryAcquire, acquire, run, stats };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * A small ring-buffer queue.\n * - O(1) pushBack / popFront\n * - avoids array shift and head-index + compaction heuristics \n */\nclass RingDeque<T> {\n private buf: Array<T | undefined>;\n private head = 0;\n private tail = 0;\n private size = 0;\n\n constructor(capacity: number) {\n const cap = Math.max(4, capacity | 0);\n this.buf = new Array<T | undefined>(cap);\n }\n\n get length() {\n return this.size;\n }\n\n pushBack(item: T) {\n if (this.size === this.buf.length) {\n this.grow();\n }\n const idx = (this.head + this.size) % this.buf.length;\n this.buf[idx] = item;\n this.size++;\n }\n\n peekFront(): T | undefined {\n if (this.size === 0) return undefined;\n return this.buf[this.head];\n }\n\n popFront(): T | undefined {\n if (this.size === 0) return undefined;\n const item = this.buf[this.head];\n this.buf[this.head] = undefined; // help GC\n this.head = (this.head + 1) % this.buf.length;\n this.size--;\n return item;\n }\n\n private grow() {\n const newBuf = new Array<T | undefined>(this.buf.length * 2);\n for (let i = 0; i < this.size; i++) {\n newBuf[i] = this.buf[(this.head + i) % this.buf.length];\n }\n this.buf = newBuf;\n this.head = 0;\n }\n}\n\nexport type BulkheadOptions = {\n maxConcurrent: number;\n maxQueue?: number; // pending waiters allowed (0 => no waiting)\n};\n\nexport type AcquireOptions = {\n signal?: AbortSignal;\n timeoutMs?: number; // waiting timeout only\n};\n\nexport type Stats = {\n inFlight: number;\n pending: number;\n maxConcurrent: number;\n maxQueue: number;\n // optional debug counters:\n aborted?: number;\n timedOut?: number;\n rejected?: number;\n doubleRelease?: number;\n};\n\nexport type Token = { release(): void };\n\nexport type TryAcquireResult =\n | { ok: true; token: Token }\n | { ok: false; reason: 'concurrency_limit' };\n\nexport type AcquireResult =\n | { ok: true; token: Token }\n | { ok: false; reason: 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted' };\n\ntype Waiter = {\n resolve: (r: AcquireResult) => void;\n cancelled: boolean;\n settled: boolean;\n\n abortListener: (() => void) | undefined;\n timeoutId: ReturnType<typeof setTimeout> | undefined;\n};\n\nexport type RejectReason = 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted';\n\nexport class BulkheadRejectedError extends Error {\n readonly code = 'BULKHEAD_REJECTED' as const;\n\n constructor(readonly reason: RejectReason) {\n super(`Bulkhead rejected: ${reason}`);\n this.name = 'BulkheadRejectedError';\n }\n}\n\nexport function createBulkhead(opts: BulkheadOptions) {\n // ---- validate ----\n if (!Number.isInteger(opts.maxConcurrent) || opts.maxConcurrent <= 0) {\n throw new Error('maxConcurrent must be a positive integer');\n }\n const maxQueue = opts.maxQueue ?? 0;\n if (!Number.isInteger(maxQueue) || maxQueue < 0) {\n throw new Error('maxQueue must be an integer >= 0');\n }\n\n // ---- state ----\n let inFlight = 0;\n\n // FIFO queue as deque (no head index, just pushBack / popFront)\n const q = new RingDeque<Waiter>(maxQueue + 1); // +1 to avoid full queue edge case\n\n // optional counters\n let aborted = 0;\n let timedOut = 0;\n let rejected = 0;\n let doubleRelease = 0;\n\n // ---- token factory ----\n const makeToken = (): Token => {\n let released = false;\n return {\n release() {\n if (released) {\n doubleRelease++;\n return; // idempotent; consider throw in dev builds if you prefer\n }\n released = true;\n inFlight--;\n if (inFlight < 0) inFlight = 0; // defensive, should never happen\n drain();\n },\n };\n };\n\n const pruneCancelledFront = () => {\n // Remove cancelled/settled waiters at the front so they stop consuming maxQueue.\n while (q.length > 0) {\n const w = q.peekFront()!;\n if (w.cancelled || w.settled) {\n cleanupWaiter(w);\n q.popFront();\n continue;\n } else {\n break;\n }\n }\n }\n\n const pendingCount = () => {\n pruneCancelledFront();\n return q.length;\n }\n\n const cleanupWaiter = (w: Waiter) => {\n if (w.abortListener) w.abortListener();\n if (w.timeoutId) clearTimeout(w.timeoutId);\n w.abortListener = undefined;\n w.timeoutId = undefined;\n };\n\n const settle = (w: Waiter, r: AcquireResult) => {\n if (w.settled) return;\n w.settled = true;\n // Once settled, it's effectively cancelled for drain-skipping purposes.\n // (We keep cancelled separate because drain checks it.)\n if (!w.cancelled && !r.ok) w.cancelled = true;\n cleanupWaiter(w);\n w.resolve(r);\n };\n\n // ---- drain algorithm ----\n const drain = () => {\n // Prune first so cancelled/settled waiters don't block the head.\n pruneCancelledFront();\n while (inFlight < opts.maxConcurrent && q.length > 0) {\n const w = q.popFront()!; \n // If it was cancelled after peek/prune but before pop, skip it.\n if (w.cancelled) {\n cleanupWaiter(w);\n pruneCancelledFront(); // in case there are more cancelled after this one\n continue;\n }\n inFlight++;\n settle(w, { ok: true, token: makeToken() });\n }\n };\n\n // ---- public APIs ----\n\n const tryAcquire = (): TryAcquireResult => {\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return { ok: true, token: makeToken() };\n }\n return { ok: false, reason: 'concurrency_limit' };\n };\n\n const acquire = (ao: AcquireOptions = {}): Promise<AcquireResult> => {\n // immediate fast path\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return Promise.resolve({ ok: true, token: makeToken() });\n }\n\n // no waiting allowed\n if (maxQueue === 0) {\n rejected++;\n return Promise.resolve({ ok: false, reason: 'concurrency_limit' });\n }\n\n // bounded waiting\n if (pendingCount() >= maxQueue) {\n rejected++;\n return Promise.resolve({ ok: false, reason: 'queue_limit' });\n }\n\n // enqueue\n return new Promise<AcquireResult>((resolve) => {\n const w: Waiter = {\n resolve,\n cancelled: false,\n settled: false,\n abortListener: undefined,\n timeoutId: undefined,\n };\n\n // abort support\n if (ao.signal) {\n if (ao.signal.aborted) {\n aborted++;\n settle(w, { ok: false, reason: 'aborted' });\n return;\n }\n const onAbort = () => {\n // mark cancelled; drain() will skip it\n aborted++;\n w.cancelled = true;\n settle(w, { ok: false, reason: 'aborted' });\n };\n\n ao.signal.addEventListener('abort', onAbort, { once: true });\n w.abortListener = () => ao.signal!.removeEventListener('abort', onAbort);\n }\n\n // timeout support (waiting only)\n if (ao.timeoutMs != null) {\n if (!Number.isFinite(ao.timeoutMs) || ao.timeoutMs < 0) {\n // invalid => treat as immediate timeout\n timedOut++;\n settle(w, { ok: false, reason: 'timeout' });\n return;\n }\n w.timeoutId = setTimeout(() => {\n timedOut++;\n w.cancelled = true;\n settle(w, { ok: false, reason: 'timeout' });\n }, ao.timeoutMs);\n }\n\n q.pushBack(w);\n drain(); // required: capacity may have freed after the fast-path check but before enqueue\n });\n };\n\n const run = async <T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n ao: AcquireOptions = {},\n ): Promise<T> => {\n const r = await acquire(ao);\n if (!r.ok) {\n throw new BulkheadRejectedError(r.reason);\n }\n try {\n return await fn(ao.signal);\n } finally {\n r.token.release();\n }\n };\n\n const stats = (): Stats => ({\n inFlight,\n pending: pendingCount(),\n maxConcurrent: opts.maxConcurrent,\n maxQueue,\n aborted,\n timedOut,\n rejected,\n doubleRelease,\n });\n\n return { tryAcquire, acquire, run, stats };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "async-bulkhead-ts",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Fail-fast admission control + bulkheads for async workloads",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,19 +10,17 @@
10
10
  ],
11
11
  "main": "./dist/index.cjs",
12
12
  "module": "./dist/index.js",
13
- "types": "./dist/index.d.ts",
14
13
  "exports": {
15
14
  ".": {
16
15
  "import": {
17
- "types": "./dist/index.d.ts",
18
16
  "default": "./dist/index.js"
19
17
  },
20
18
  "require": {
21
- "types": "./dist/index.d.cts",
22
19
  "default": "./dist/index.cjs"
23
20
  }
24
21
  }
25
22
  },
23
+ "types": "./dist/index.d.ts",
26
24
  "engines": {
27
25
  "node": ">=20"
28
26
  },