async-bulkhead-ts 0.2.0 → 0.2.1
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 +86 -52
- package/dist/index.cjs +40 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +40 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# async-bulkhead-ts
|
|
2
2
|
|
|
3
|
-
Fail-fast **admission control**
|
|
3
|
+
Fail-fast **admission control** (async bulkheads) for Node.js / TypeScript.
|
|
4
4
|
|
|
5
|
-
Designed for services that prefer **rejecting work early** over
|
|
5
|
+
Designed for services that prefer **rejecting work early** over silently degrading via growing queues and timeouts.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- ✅ **
|
|
12
|
-
- ✅
|
|
13
|
-
- ✅
|
|
14
|
-
- ✅
|
|
15
|
-
- ✅
|
|
16
|
-
- ✅
|
|
11
|
+
- ✅ Hard **max in-flight** concurrency (`maxConcurrent`)
|
|
12
|
+
- ✅ Optional **bounded FIFO waiting** (`maxQueue`)
|
|
13
|
+
- ✅ **Fail-fast by default** (`maxQueue: 0`)
|
|
14
|
+
- ✅ Explicit acquire + release via a **token**
|
|
15
|
+
- ✅ `bulkhead.run(fn)` helper (acquire + `finally` release)
|
|
16
|
+
- ✅ Optional waiting **timeout** and **AbortSignal** cancellation
|
|
17
17
|
- ✅ Zero dependencies
|
|
18
18
|
- ✅ ESM + CJS support
|
|
19
19
|
- ✅ Node.js **20+**
|
|
@@ -22,7 +22,6 @@ Non-goals (by design):
|
|
|
22
22
|
- ❌ No background workers
|
|
23
23
|
- ❌ No retry logic
|
|
24
24
|
- ❌ No distributed coordination
|
|
25
|
-
- ❌ No built-in metrics backend (hooks only)
|
|
26
25
|
|
|
27
26
|
---
|
|
28
27
|
|
|
@@ -32,49 +31,55 @@ Non-goals (by design):
|
|
|
32
31
|
npm install async-bulkhead-ts
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
## Basic Usage (Manual)
|
|
34
|
+
## Basic Usage (Manual acquire/release)
|
|
36
35
|
|
|
37
36
|
```ts
|
|
38
37
|
import { createBulkhead } from 'async-bulkhead-ts';
|
|
39
38
|
|
|
40
|
-
const bulkhead = createBulkhead({
|
|
41
|
-
|
|
39
|
+
const bulkhead = createBulkhead({
|
|
40
|
+
maxConcurrent: 10,
|
|
41
|
+
});
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
const r = await bulkhead.acquire();
|
|
44
|
+
|
|
45
|
+
if (!r.ok) {
|
|
44
46
|
// Fail fast — shed load, return 503, etc.
|
|
45
|
-
|
|
47
|
+
// r.reason is one of:
|
|
48
|
+
// 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted'
|
|
49
|
+
throw new Error(`Rejected: ${r.reason}`);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
try {
|
|
49
53
|
await doWork();
|
|
50
54
|
} finally {
|
|
51
|
-
|
|
55
|
+
r.token.release();
|
|
52
56
|
}
|
|
53
57
|
```
|
|
54
58
|
|
|
55
|
-
You must call `release()` exactly once if
|
|
56
|
-
Failing to release permanently reduces capacity.
|
|
59
|
+
You must call `token.release()` exactly once if acquisition succeeds.
|
|
60
|
+
Failing to release permanently reduces available capacity.
|
|
57
61
|
|
|
58
62
|
## Convenience Helper
|
|
59
63
|
|
|
60
64
|
For most use cases, prefer `bulkhead.run(fn)`:
|
|
61
65
|
|
|
62
66
|
```ts
|
|
63
|
-
await bulkhead.run(async () =>
|
|
64
|
-
await doWork();
|
|
65
|
-
});
|
|
67
|
+
await bulkhead.run(async () => doWork());
|
|
66
68
|
```
|
|
67
69
|
|
|
68
70
|
Behavior:
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
- Acquire + release handled automatically (finally release)
|
|
73
|
+
- Still fail-fast (unless you configure `maxQueue`)
|
|
74
|
+
- Rejections throw a typed `BulkheadRejectedError` when using `run()`
|
|
75
|
+
- The provided `AbortSignal` is passed through to the function
|
|
76
|
+
- Supports waiting cancellation via `AbortSignal` and `timeoutMs`
|
|
77
|
+
|
|
78
|
+
> The signal passed to `run()` only affects admission and observation; in-flight work is not forcibly cancelled.
|
|
74
79
|
|
|
75
80
|
## With a Queue (Optional)
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
Waiting is opt-in and bounded (FIFO).
|
|
78
83
|
|
|
79
84
|
```ts
|
|
80
85
|
const bulkhead = createBulkhead({
|
|
@@ -83,28 +88,32 @@ const bulkhead = createBulkhead({
|
|
|
83
88
|
});
|
|
84
89
|
```
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
Semantics:
|
|
92
|
+
- If `inFlight` < `maxConcurrent`: `acquire()` succeeds immediately.
|
|
93
|
+
- Else if `maxQueue` > 0 and queue has space: `acquire()` waits FIFO.
|
|
94
|
+
- Else: rejected immediately.
|
|
89
95
|
|
|
90
96
|
## Cancellation
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
Waiting can be cancelled with an `AbortSignal`:
|
|
93
99
|
|
|
94
100
|
```ts
|
|
95
101
|
await bulkhead.run(
|
|
96
|
-
async () =>
|
|
97
|
-
await doWork();
|
|
98
|
-
},
|
|
102
|
+
async () => doWork(),
|
|
99
103
|
{ signal }
|
|
100
104
|
);
|
|
101
105
|
```
|
|
102
106
|
|
|
103
107
|
Cancellation guarantees:
|
|
108
|
+
- Work that is waiting in the queue can be cancelled before it starts.
|
|
109
|
+
- In-flight work is not forcibly terminated (your function may observe the signal).
|
|
110
|
+
- Capacity is always released correctly for acquired tokens.
|
|
111
|
+
|
|
112
|
+
You can also bound waiting time:
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
```ts
|
|
115
|
+
await bulkhead.run(async () => doWork(), { timeoutMs: 50 });
|
|
116
|
+
```
|
|
108
117
|
|
|
109
118
|
## API
|
|
110
119
|
|
|
@@ -113,7 +122,7 @@ Cancellation guarantees:
|
|
|
113
122
|
```ts
|
|
114
123
|
type BulkheadOptions = {
|
|
115
124
|
maxConcurrent: number;
|
|
116
|
-
maxQueue?: number;
|
|
125
|
+
maxQueue?: number; // pending waiters allowed (0 => no waiting)
|
|
117
126
|
};
|
|
118
127
|
```
|
|
119
128
|
|
|
@@ -121,38 +130,62 @@ Returns:
|
|
|
121
130
|
|
|
122
131
|
```ts
|
|
123
132
|
{
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
tryAcquire(): TryAcquireResult;
|
|
134
|
+
acquire(options?): Promise<AcquireResult>;
|
|
135
|
+
run<T>(fn: (signal?: AbortSignal) => Promise<T>, options?): Promise<T>;
|
|
126
136
|
stats(): {
|
|
127
137
|
inFlight: number;
|
|
128
|
-
|
|
138
|
+
pending: number;
|
|
129
139
|
maxConcurrent: number;
|
|
130
140
|
maxQueue: number;
|
|
141
|
+
aborted?: number;
|
|
142
|
+
timedOut?: number;
|
|
143
|
+
rejected?: number;
|
|
144
|
+
doubleRelease?: number;
|
|
131
145
|
};
|
|
132
146
|
}
|
|
133
147
|
```
|
|
134
148
|
|
|
135
|
-
`
|
|
149
|
+
`tryAcquire()`
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
export type TryAcquireResult =
|
|
153
|
+
| { ok: true; token: Token }
|
|
154
|
+
| { ok: false; reason: 'concurrency_limit' };
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
> `tryAcquire()` never waits and never enqueues; it either acquires immediately or fails fast.
|
|
158
|
+
|
|
159
|
+
`acquire(options?)`
|
|
136
160
|
|
|
137
161
|
```ts
|
|
138
|
-
type
|
|
139
|
-
|
|
140
|
-
|
|
162
|
+
type AcquireOptions = {
|
|
163
|
+
signal?: AbortSignal;
|
|
164
|
+
timeoutMs?: number; // waiting timeout only
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
type AcquireResult =
|
|
168
|
+
| { ok: true; token: Token }
|
|
169
|
+
| { ok: false; reason: 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted' };
|
|
141
170
|
```
|
|
142
171
|
|
|
143
|
-
|
|
172
|
+
`run(fn, options?)`
|
|
144
173
|
|
|
145
|
-
|
|
174
|
+
Throws on rejection:
|
|
146
175
|
|
|
147
|
-
|
|
176
|
+
```ts
|
|
177
|
+
class BulkheadRejectedError extends Error {
|
|
178
|
+
readonly code = 'BULKHEAD_REJECTED';
|
|
179
|
+
readonly reason: 'concurrency_limit' | 'queue_limit' | 'timeout' | 'aborted';
|
|
180
|
+
}
|
|
181
|
+
```
|
|
148
182
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
* execution started
|
|
152
|
-
* released
|
|
153
|
-
* rejected
|
|
183
|
+
The function passed to `run()` receives the same `AbortSignal` (if provided), allowing
|
|
184
|
+
in-flight work to observe cancellation:
|
|
154
185
|
|
|
155
|
-
|
|
186
|
+
```ts
|
|
187
|
+
await bulkhead.run(async (signal) => doWork(signal), { signal });
|
|
188
|
+
```
|
|
156
189
|
|
|
157
190
|
## Design Philosophy
|
|
158
191
|
|
|
@@ -171,6 +204,7 @@ If you need retries, buffering, scheduling, or persistence—compose those aroun
|
|
|
171
204
|
* Node.js: 20+ (24 LTS recommended)
|
|
172
205
|
* Module formats: ESM and CommonJS
|
|
173
206
|
|
|
207
|
+
|
|
174
208
|
## License
|
|
175
209
|
|
|
176
210
|
MIT © 2026
|
package/dist/index.cjs
CHANGED
|
@@ -47,7 +47,25 @@ function createBulkhead(opts) {
|
|
|
47
47
|
let timedOut = 0;
|
|
48
48
|
let rejected = 0;
|
|
49
49
|
let doubleRelease = 0;
|
|
50
|
-
const
|
|
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
|
+
};
|
|
51
69
|
const makeToken = () => {
|
|
52
70
|
let released = false;
|
|
53
71
|
return {
|
|
@@ -69,20 +87,23 @@ function createBulkhead(opts) {
|
|
|
69
87
|
w.abortListener = void 0;
|
|
70
88
|
w.timeoutId = void 0;
|
|
71
89
|
};
|
|
90
|
+
const settle = (w, r) => {
|
|
91
|
+
if (w.settled) return;
|
|
92
|
+
w.settled = true;
|
|
93
|
+
if (!w.cancelled && !r.ok) w.cancelled = true;
|
|
94
|
+
cleanupWaiter(w);
|
|
95
|
+
w.resolve(r);
|
|
96
|
+
};
|
|
72
97
|
const drain = () => {
|
|
98
|
+
pruneHead();
|
|
73
99
|
while (inFlight < opts.maxConcurrent && pendingCount() > 0) {
|
|
74
100
|
const w = q[qHead++];
|
|
75
|
-
if (w.cancelled) {
|
|
101
|
+
if (w.cancelled || w.settled) {
|
|
76
102
|
cleanupWaiter(w);
|
|
77
103
|
continue;
|
|
78
104
|
}
|
|
79
105
|
inFlight++;
|
|
80
|
-
|
|
81
|
-
w.resolve({ ok: true, token: makeToken() });
|
|
82
|
-
}
|
|
83
|
-
if (qHead > 1024 && qHead * 2 > q.length) {
|
|
84
|
-
q.splice(0, qHead);
|
|
85
|
-
qHead = 0;
|
|
106
|
+
settle(w, { ok: true, token: makeToken() });
|
|
86
107
|
}
|
|
87
108
|
};
|
|
88
109
|
const tryAcquire = () => {
|
|
@@ -90,12 +111,7 @@ function createBulkhead(opts) {
|
|
|
90
111
|
inFlight++;
|
|
91
112
|
return { ok: true, token: makeToken() };
|
|
92
113
|
}
|
|
93
|
-
|
|
94
|
-
rejected++;
|
|
95
|
-
return { ok: false, reason: "queue_limit" };
|
|
96
|
-
}
|
|
97
|
-
rejected++;
|
|
98
|
-
return { ok: false, reason: maxQueue > 0 ? "queue_limit" : "concurrency_limit" };
|
|
114
|
+
return { ok: false, reason: "concurrency_limit" };
|
|
99
115
|
};
|
|
100
116
|
const acquire = (ao = {}) => {
|
|
101
117
|
if (inFlight < opts.maxConcurrent) {
|
|
@@ -114,22 +130,20 @@ function createBulkhead(opts) {
|
|
|
114
130
|
const w = {
|
|
115
131
|
resolve,
|
|
116
132
|
cancelled: false,
|
|
133
|
+
settled: false,
|
|
117
134
|
abortListener: void 0,
|
|
118
135
|
timeoutId: void 0
|
|
119
136
|
};
|
|
120
137
|
if (ao.signal) {
|
|
121
138
|
if (ao.signal.aborted) {
|
|
122
139
|
aborted++;
|
|
123
|
-
|
|
140
|
+
settle(w, { ok: false, reason: "aborted" });
|
|
124
141
|
return;
|
|
125
142
|
}
|
|
126
143
|
const onAbort = () => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
cleanupWaiter(w);
|
|
131
|
-
resolve({ ok: false, reason: "aborted" });
|
|
132
|
-
}
|
|
144
|
+
aborted++;
|
|
145
|
+
w.cancelled = true;
|
|
146
|
+
settle(w, { ok: false, reason: "aborted" });
|
|
133
147
|
};
|
|
134
148
|
ao.signal.addEventListener("abort", onAbort, { once: true });
|
|
135
149
|
w.abortListener = () => ao.signal.removeEventListener("abort", onAbort);
|
|
@@ -137,19 +151,17 @@ function createBulkhead(opts) {
|
|
|
137
151
|
if (ao.timeoutMs != null) {
|
|
138
152
|
if (!Number.isFinite(ao.timeoutMs) || ao.timeoutMs < 0) {
|
|
139
153
|
timedOut++;
|
|
140
|
-
|
|
154
|
+
settle(w, { ok: false, reason: "timeout" });
|
|
141
155
|
return;
|
|
142
156
|
}
|
|
143
157
|
w.timeoutId = setTimeout(() => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
cleanupWaiter(w);
|
|
148
|
-
resolve({ ok: false, reason: "timeout" });
|
|
149
|
-
}
|
|
158
|
+
timedOut++;
|
|
159
|
+
w.cancelled = true;
|
|
160
|
+
settle(w, { ok: false, reason: "timeout" });
|
|
150
161
|
}, ao.timeoutMs);
|
|
151
162
|
}
|
|
152
163
|
q.push(w);
|
|
164
|
+
drain();
|
|
153
165
|
});
|
|
154
166
|
};
|
|
155
167
|
const run = async (fn, ao = {}) => {
|
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' | 'queue_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\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 pendingCount = () => q.length - qHead;\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 // ---- drain algorithm ----\n const drain = () => {\n while (inFlight < opts.maxConcurrent && pendingCount() > 0) {\n const w = q[qHead++]!;\n // skip cancelled waiters\n if (w.cancelled) {\n cleanupWaiter(w);\n continue;\n }\n\n // grant slot\n inFlight++;\n cleanupWaiter(w);\n w.resolve({ ok: true, token: makeToken() });\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 // ---- public APIs ----\n\n const tryAcquire = (): TryAcquireResult => {\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return { ok: true, token: makeToken() };\n }\n // tryAcquire never waits; queue_limit matters only if maxQueue configured\n if (maxQueue > 0 && pendingCount() >= maxQueue) {\n rejected++;\n return { ok: false, reason: 'queue_limit' };\n }\n rejected++;\n return { ok: false, reason: maxQueue > 0 ? 'queue_limit' : '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 abortListener: undefined,\n timeoutId: undefined,\n };\n\n // abort support\n if (ao.signal) {\n if (ao.signal.aborted) {\n aborted++;\n resolve({ ok: false, reason: 'aborted' });\n return;\n }\n const onAbort = () => {\n // mark cancelled; drain() will skip it\n if (!w.cancelled) {\n w.cancelled = true;\n aborted++;\n cleanupWaiter(w);\n resolve({ 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 resolve({ ok: false, reason: 'timeout' });\n return;\n }\n w.timeoutId = setTimeout(() => {\n if (!w.cancelled) {\n w.cancelled = true;\n timedOut++;\n cleanupWaiter(w);\n resolve({ ok: false, reason: 'timeout' });\n }\n }, ao.timeoutMs);\n }\n\n q.push(w);\n // NOTE: we do NOT reserve capacity here.\n // A slot is consumed only when drain() grants a token.\n\n // No need to call drain() here because we already know we’re at capacity,\n // but it doesn’t hurt if you want (for races with release).\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":["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":[]}
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -22,7 +22,25 @@ function createBulkhead(opts) {
|
|
|
22
22
|
let timedOut = 0;
|
|
23
23
|
let rejected = 0;
|
|
24
24
|
let doubleRelease = 0;
|
|
25
|
-
const
|
|
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
|
+
};
|
|
26
44
|
const makeToken = () => {
|
|
27
45
|
let released = false;
|
|
28
46
|
return {
|
|
@@ -44,20 +62,23 @@ function createBulkhead(opts) {
|
|
|
44
62
|
w.abortListener = void 0;
|
|
45
63
|
w.timeoutId = void 0;
|
|
46
64
|
};
|
|
65
|
+
const settle = (w, r) => {
|
|
66
|
+
if (w.settled) return;
|
|
67
|
+
w.settled = true;
|
|
68
|
+
if (!w.cancelled && !r.ok) w.cancelled = true;
|
|
69
|
+
cleanupWaiter(w);
|
|
70
|
+
w.resolve(r);
|
|
71
|
+
};
|
|
47
72
|
const drain = () => {
|
|
73
|
+
pruneHead();
|
|
48
74
|
while (inFlight < opts.maxConcurrent && pendingCount() > 0) {
|
|
49
75
|
const w = q[qHead++];
|
|
50
|
-
if (w.cancelled) {
|
|
76
|
+
if (w.cancelled || w.settled) {
|
|
51
77
|
cleanupWaiter(w);
|
|
52
78
|
continue;
|
|
53
79
|
}
|
|
54
80
|
inFlight++;
|
|
55
|
-
|
|
56
|
-
w.resolve({ ok: true, token: makeToken() });
|
|
57
|
-
}
|
|
58
|
-
if (qHead > 1024 && qHead * 2 > q.length) {
|
|
59
|
-
q.splice(0, qHead);
|
|
60
|
-
qHead = 0;
|
|
81
|
+
settle(w, { ok: true, token: makeToken() });
|
|
61
82
|
}
|
|
62
83
|
};
|
|
63
84
|
const tryAcquire = () => {
|
|
@@ -65,12 +86,7 @@ function createBulkhead(opts) {
|
|
|
65
86
|
inFlight++;
|
|
66
87
|
return { ok: true, token: makeToken() };
|
|
67
88
|
}
|
|
68
|
-
|
|
69
|
-
rejected++;
|
|
70
|
-
return { ok: false, reason: "queue_limit" };
|
|
71
|
-
}
|
|
72
|
-
rejected++;
|
|
73
|
-
return { ok: false, reason: maxQueue > 0 ? "queue_limit" : "concurrency_limit" };
|
|
89
|
+
return { ok: false, reason: "concurrency_limit" };
|
|
74
90
|
};
|
|
75
91
|
const acquire = (ao = {}) => {
|
|
76
92
|
if (inFlight < opts.maxConcurrent) {
|
|
@@ -89,22 +105,20 @@ function createBulkhead(opts) {
|
|
|
89
105
|
const w = {
|
|
90
106
|
resolve,
|
|
91
107
|
cancelled: false,
|
|
108
|
+
settled: false,
|
|
92
109
|
abortListener: void 0,
|
|
93
110
|
timeoutId: void 0
|
|
94
111
|
};
|
|
95
112
|
if (ao.signal) {
|
|
96
113
|
if (ao.signal.aborted) {
|
|
97
114
|
aborted++;
|
|
98
|
-
|
|
115
|
+
settle(w, { ok: false, reason: "aborted" });
|
|
99
116
|
return;
|
|
100
117
|
}
|
|
101
118
|
const onAbort = () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
cleanupWaiter(w);
|
|
106
|
-
resolve({ ok: false, reason: "aborted" });
|
|
107
|
-
}
|
|
119
|
+
aborted++;
|
|
120
|
+
w.cancelled = true;
|
|
121
|
+
settle(w, { ok: false, reason: "aborted" });
|
|
108
122
|
};
|
|
109
123
|
ao.signal.addEventListener("abort", onAbort, { once: true });
|
|
110
124
|
w.abortListener = () => ao.signal.removeEventListener("abort", onAbort);
|
|
@@ -112,19 +126,17 @@ function createBulkhead(opts) {
|
|
|
112
126
|
if (ao.timeoutMs != null) {
|
|
113
127
|
if (!Number.isFinite(ao.timeoutMs) || ao.timeoutMs < 0) {
|
|
114
128
|
timedOut++;
|
|
115
|
-
|
|
129
|
+
settle(w, { ok: false, reason: "timeout" });
|
|
116
130
|
return;
|
|
117
131
|
}
|
|
118
132
|
w.timeoutId = setTimeout(() => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
cleanupWaiter(w);
|
|
123
|
-
resolve({ ok: false, reason: "timeout" });
|
|
124
|
-
}
|
|
133
|
+
timedOut++;
|
|
134
|
+
w.cancelled = true;
|
|
135
|
+
settle(w, { ok: false, reason: "timeout" });
|
|
125
136
|
}, ao.timeoutMs);
|
|
126
137
|
}
|
|
127
138
|
q.push(w);
|
|
139
|
+
drain();
|
|
128
140
|
});
|
|
129
141
|
};
|
|
130
142
|
const run = async (fn, ao = {}) => {
|
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' | 'queue_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\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 pendingCount = () => q.length - qHead;\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 // ---- drain algorithm ----\n const drain = () => {\n while (inFlight < opts.maxConcurrent && pendingCount() > 0) {\n const w = q[qHead++]!;\n // skip cancelled waiters\n if (w.cancelled) {\n cleanupWaiter(w);\n continue;\n }\n\n // grant slot\n inFlight++;\n cleanupWaiter(w);\n w.resolve({ ok: true, token: makeToken() });\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 // ---- public APIs ----\n\n const tryAcquire = (): TryAcquireResult => {\n if (inFlight < opts.maxConcurrent) {\n inFlight++;\n return { ok: true, token: makeToken() };\n }\n // tryAcquire never waits; queue_limit matters only if maxQueue configured\n if (maxQueue > 0 && pendingCount() >= maxQueue) {\n rejected++;\n return { ok: false, reason: 'queue_limit' };\n }\n rejected++;\n return { ok: false, reason: maxQueue > 0 ? 'queue_limit' : '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 abortListener: undefined,\n timeoutId: undefined,\n };\n\n // abort support\n if (ao.signal) {\n if (ao.signal.aborted) {\n aborted++;\n resolve({ ok: false, reason: 'aborted' });\n return;\n }\n const onAbort = () => {\n // mark cancelled; drain() will skip it\n if (!w.cancelled) {\n w.cancelled = true;\n aborted++;\n cleanupWaiter(w);\n resolve({ 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 resolve({ ok: false, reason: 'timeout' });\n return;\n }\n w.timeoutId = setTimeout(() => {\n if (!w.cancelled) {\n w.cancelled = true;\n timedOut++;\n cleanupWaiter(w);\n resolve({ ok: false, reason: 'timeout' });\n }\n }, ao.timeoutMs);\n }\n\n q.push(w);\n // NOTE: we do NOT reserve capacity here.\n // A slot is consumed only when drain() grants a token.\n\n // No need to call drain() here because we already know we’re at capacity,\n // but it doesn’t hurt if you want (for races with release).\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":["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":[]}
|