async-bulkhead-ts 0.2.1 → 0.2.3
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 +13 -0
- package/dist/index.cjs +64 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +64 -26
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
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
|
-
|
|
99
|
-
while (inFlight < opts.maxConcurrent &&
|
|
100
|
-
const w = q
|
|
101
|
-
if (w.cancelled
|
|
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.
|
|
201
|
+
q.pushBack(w);
|
|
164
202
|
drain();
|
|
165
203
|
});
|
|
166
204
|
};
|
package/dist/index.cjs.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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,IAAM,YAAN,MAAmB;AAAA,EACT;AAAA,EACA,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EAEf,YAAY,UAAkB;AAC5B,UAAM,MAAM,KAAK,IAAI,GAAG,WAAW,CAAC;AACpC,SAAK,MAAM,IAAI,MAAqB,GAAG;AAAA,EACzC;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,SAAS,MAAS;AAChB,QAAI,KAAK,SAAS,KAAK,IAAI,QAAQ;AACjC,WAAK,KAAK;AAAA,IACZ;AACA,UAAM,OAAO,KAAK,OAAO,KAAK,QAAQ,KAAK,IAAI;AAC/C,SAAK,IAAI,GAAG,IAAI;AAChB,SAAK;AAAA,EACP;AAAA,EAEA,YAA2B;AACzB,QAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,WAAO,KAAK,IAAI,KAAK,IAAI;AAAA,EAC3B;AAAA,EAEA,WAA0B;AACxB,QAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,UAAM,OAAO,KAAK,IAAI,KAAK,IAAI;AAC/B,SAAK,IAAI,KAAK,IAAI,IAAI;AACtB,SAAK,QAAQ,KAAK,OAAO,KAAK,KAAK,IAAI;AACvC,SAAK;AACL,WAAO;AAAA,EACT;AAAA,EAEQ,OAAO;AACb,UAAM,SAAS,IAAI,MAAqB,KAAK,IAAI,SAAS,CAAC;AAC3D,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,KAAK;AAClC,aAAO,CAAC,IAAI,KAAK,KAAK,KAAK,OAAO,KAAK,KAAK,IAAI,MAAM;AAAA,IACxD;AACA,SAAK,MAAM;AACX,SAAK,OAAO;AAAA,EACd;AACF;AA6CO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAG/C,YAAqB,QAAsB;AACzC,UAAM,sBAAsB,MAAM,EAAE;AADjB;AAEnB,SAAK,OAAO;AAAA,EACd;AAAA,EALS,OAAO;AAMlB;AAEO,SAAS,eAAe,MAAuB;AAEpD,MAAI,CAAC,OAAO,UAAU,KAAK,aAAa,KAAK,KAAK,iBAAiB,GAAG;AACpE,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,QAAM,WAAW,KAAK,YAAY;AAClC,MAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,WAAW,GAAG;AAC/C,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAGA,MAAI,WAAW;AAGf,QAAM,IAAI,IAAI,UAAkB,WAAW,CAAC;AAG5C,MAAI,UAAU;AACd,MAAI,WAAW;AACf,MAAI,WAAW;AACf,MAAI,gBAAgB;AAGpB,QAAM,YAAY,MAAa;AAC7B,QAAI,WAAW;AACf,WAAO;AAAA,MACL,UAAU;AACR,YAAI,UAAU;AACZ;AACA;AAAA,QACF;AACA,mBAAW;AACX;AACA,YAAI,WAAW,EAAG,YAAW;AAC7B,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,sBAAsB,MAAM;AAEhC,WAAO,EAAE,SAAS,GAAG;AACnB,YAAM,IAAI,EAAE,UAAU;AACtB,UAAI,EAAE,aAAa,EAAE,SAAS;AAC5B,sBAAc,CAAC;AACf,UAAE,SAAS;AACX;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,MAAM;AACzB,wBAAoB;AACpB,WAAO,EAAE;AAAA,EACX;AAEA,QAAM,gBAAgB,CAAC,MAAc;AACnC,QAAI,EAAE,cAAe,GAAE,cAAc;AACrC,QAAI,EAAE,UAAW,cAAa,EAAE,SAAS;AACzC,MAAE,gBAAgB;AAClB,MAAE,YAAY;AAAA,EAChB;AAEA,QAAM,SAAS,CAAC,GAAW,MAAqB;AAC9C,QAAI,EAAE,QAAS;AACf,MAAE,UAAU;AAGZ,QAAI,CAAC,EAAE,aAAa,CAAC,EAAE,GAAI,GAAE,YAAY;AACzC,kBAAc,CAAC;AACf,MAAE,QAAQ,CAAC;AAAA,EACb;AAGA,QAAM,QAAQ,MAAM;AAElB,wBAAoB;AACpB,WAAO,WAAW,KAAK,iBAAiB,EAAE,SAAS,GAAG;AACpD,YAAM,IAAI,EAAE,SAAS;AAErB,UAAI,EAAE,WAAW;AACf,sBAAc,CAAC;AACf,4BAAoB;AACpB;AAAA,MACF;AACA;AACA,aAAO,GAAG,EAAE,IAAI,MAAM,OAAO,UAAU,EAAE,CAAC;AAAA,IAC5C;AAAA,EACF;AAIA,QAAM,aAAa,MAAwB;AACzC,QAAI,WAAW,KAAK,eAAe;AACjC;AACA,aAAO,EAAE,IAAI,MAAM,OAAO,UAAU,EAAE;AAAA,IACxC;AACA,WAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AAAA,EAClD;AAEA,QAAM,UAAU,CAAC,KAAqB,CAAC,MAA8B;AAEnE,QAAI,WAAW,KAAK,eAAe;AACjC;AACA,aAAO,QAAQ,QAAQ,EAAE,IAAI,MAAM,OAAO,UAAU,EAAE,CAAC;AAAA,IACzD;AAGA,QAAI,aAAa,GAAG;AAClB;AACA,aAAO,QAAQ,QAAQ,EAAE,IAAI,OAAO,QAAQ,oBAAoB,CAAC;AAAA,IACnE;AAGA,QAAI,aAAa,KAAK,UAAU;AAC9B;AACA,aAAO,QAAQ,QAAQ,EAAE,IAAI,OAAO,QAAQ,cAAc,CAAC;AAAA,IAC7D;AAGA,WAAO,IAAI,QAAuB,CAAC,YAAY;AAC7C,YAAM,IAAY;AAAA,QAChB;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,eAAe;AAAA,QACf,WAAW;AAAA,MACb;AAGA,UAAI,GAAG,QAAQ;AACb,YAAI,GAAG,OAAO,SAAS;AACrB;AACA,iBAAO,GAAG,EAAE,IAAI,OAAO,QAAQ,UAAU,CAAC;AAC1C;AAAA,QACF;AACA,cAAM,UAAU,MAAM;AAEpB;AACA,YAAE,YAAY;AACd,iBAAO,GAAG,EAAE,IAAI,OAAO,QAAQ,UAAU,CAAC;AAAA,QAC5C;AAEA,WAAG,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAC3D,UAAE,gBAAgB,MAAM,GAAG,OAAQ,oBAAoB,SAAS,OAAO;AAAA,MACzE;AAGA,UAAI,GAAG,aAAa,MAAM;AACxB,YAAI,CAAC,OAAO,SAAS,GAAG,SAAS,KAAK,GAAG,YAAY,GAAG;AAEtD;AACA,iBAAO,GAAG,EAAE,IAAI,OAAO,QAAQ,UAAU,CAAC;AAC1C;AAAA,QACF;AACA,UAAE,YAAY,WAAW,MAAM;AAC7B;AACA,YAAE,YAAY;AACd,iBAAO,GAAG,EAAE,IAAI,OAAO,QAAQ,UAAU,CAAC;AAAA,QAC5C,GAAG,GAAG,SAAS;AAAA,MACjB;AAEA,QAAE,SAAS,CAAC;AACZ,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,QAAM,MAAM,OACV,IACA,KAAqB,CAAC,MACP;AACf,UAAM,IAAI,MAAM,QAAQ,EAAE;AAC1B,QAAI,CAAC,EAAE,IAAI;AACT,YAAM,IAAI,sBAAsB,EAAE,MAAM;AAAA,IAC1C;AACA,QAAI;AACF,aAAO,MAAM,GAAG,GAAG,MAAM;AAAA,IAC3B,UAAE;AACA,QAAE,MAAM,QAAQ;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,QAAQ,OAAc;AAAA,IAC1B;AAAA,IACA,SAAS,aAAa;AAAA,IACtB,eAAe,KAAK;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,SAAS,KAAK,MAAM;AAC3C;","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
|
-
|
|
74
|
-
while (inFlight < opts.maxConcurrent &&
|
|
75
|
-
const w = q
|
|
76
|
-
if (w.cancelled
|
|
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.
|
|
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,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "async-bulkhead-ts",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"overrides": {
|
|
5
|
+
"minimatch": "^10.2.1",
|
|
6
|
+
"@eslint/eslintrc": {
|
|
7
|
+
"ajv": "^6.12.6"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
4
10
|
"description": "Fail-fast admission control + bulkheads for async workloads",
|
|
5
11
|
"license": "MIT",
|
|
6
12
|
"type": "module",
|
|
@@ -10,19 +16,17 @@
|
|
|
10
16
|
],
|
|
11
17
|
"main": "./dist/index.cjs",
|
|
12
18
|
"module": "./dist/index.js",
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
14
19
|
"exports": {
|
|
15
20
|
".": {
|
|
16
21
|
"import": {
|
|
17
|
-
"types": "./dist/index.d.ts",
|
|
18
22
|
"default": "./dist/index.js"
|
|
19
23
|
},
|
|
20
24
|
"require": {
|
|
21
|
-
"types": "./dist/index.d.cts",
|
|
22
25
|
"default": "./dist/index.cjs"
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
},
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
26
30
|
"engines": {
|
|
27
31
|
"node": ">=20"
|
|
28
32
|
},
|