@peerbit/stream 4.4.0-58d3d09 → 4.4.0-780f7ce
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/dist/src/index.d.ts +10 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +79 -9
- package/dist/src/index.js.map +1 -1
- package/dist/src/pushable-lanes.d.ts +48 -22
- package/dist/src/pushable-lanes.d.ts.map +1 -1
- package/dist/src/pushable-lanes.js +183 -57
- package/dist/src/pushable-lanes.js.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +108 -11
- package/src/pushable-lanes.ts +285 -116
package/src/pushable-lanes.ts
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
|
+
// pushable-lanes.ts
|
|
2
|
+
// A multi-lane async pushable with starvation-free scheduling.
|
|
3
|
+
// Inspired by it-pushable (MIT) and extended for multi-lane fairness.
|
|
4
|
+
//
|
|
5
|
+
// Features:
|
|
6
|
+
// - Async iterator you can .push() into
|
|
7
|
+
// - N lanes (priorities) with starvation-free scheduling (Weighted Round-Robin)
|
|
8
|
+
// - Optional strict priority mode for legacy behavior
|
|
9
|
+
// - Optional high-water mark (bytes) with overflow policy
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// const p = pushableLanes<Uint8Array>({ lanes: 2 }); // default fairness = 'wrr'
|
|
13
|
+
// p.push(new Uint8Array([1]), 1); // slower lane
|
|
14
|
+
// p.push(new Uint8Array([0]), 0); // faster lane
|
|
15
|
+
// for await (const chunk of p) { ... }
|
|
16
|
+
//
|
|
17
|
+
// // Backpressure example (throw if > 8MB buffered):
|
|
18
|
+
// const q = pushableLanes<Uint8Array>({ lanes: 3, maxBufferedBytes: 8 * 1024 * 1024, overflow: 'throw' });
|
|
19
|
+
//
|
|
20
|
+
// Notes:
|
|
21
|
+
// - T must have a .byteLength number property (e.g. Uint8Array).
|
|
22
|
+
// - Lane indices are 0..(lanes-1). Defaults to lane 0.
|
|
1
23
|
import GenericFIFO from "fast-fifo";
|
|
2
24
|
import defer from "p-defer";
|
|
3
25
|
|
|
26
|
+
// -----------------------------
|
|
27
|
+
// Errors & shared option types
|
|
28
|
+
// -----------------------------
|
|
29
|
+
|
|
4
30
|
export class AbortError extends Error {
|
|
5
31
|
type: string;
|
|
6
32
|
code: string;
|
|
@@ -16,66 +42,107 @@ export interface AbortOptions {
|
|
|
16
42
|
signal?: AbortSignal;
|
|
17
43
|
}
|
|
18
44
|
|
|
45
|
+
// -----------------------------
|
|
46
|
+
// Public API interfaces
|
|
47
|
+
// -----------------------------
|
|
48
|
+
|
|
19
49
|
/**
|
|
20
50
|
* An iterable that you can push values into.
|
|
21
51
|
*/
|
|
22
52
|
export interface PushableLanes<T, R = void, N = unknown>
|
|
23
53
|
extends AsyncGenerator<T, R, N> {
|
|
24
54
|
/**
|
|
25
|
-
* End the iterable after all values in the buffer (if any) have been yielded.
|
|
26
|
-
* error is passed the buffer is cleared immediately and the next
|
|
27
|
-
* throw the passed error
|
|
55
|
+
* End the iterable after all values in the buffer (if any) have been yielded.
|
|
56
|
+
* If an error is passed, the buffer is cleared immediately and the next
|
|
57
|
+
* iteration will throw the passed error.
|
|
28
58
|
*/
|
|
29
59
|
end(err?: Error): this;
|
|
30
60
|
|
|
31
61
|
/**
|
|
32
|
-
* Push a value into the iterable. Values are yielded
|
|
33
|
-
*
|
|
62
|
+
* Push a value into the iterable. Values are yielded in a lane-aware order.
|
|
63
|
+
* Values not yet consumed are buffered. Optional `lane` is 0-based (default 0).
|
|
34
64
|
*/
|
|
35
65
|
push(value: T, lane?: number): this;
|
|
36
66
|
|
|
37
67
|
/**
|
|
38
|
-
*
|
|
39
|
-
* this
|
|
40
|
-
*
|
|
41
|
-
* If an AbortSignal is passed as an option and that signal aborts, it only
|
|
42
|
-
* causes the returned promise to reject - it does not end the pushable.
|
|
68
|
+
* Resolves when the underlying buffer becomes empty (no queued data).
|
|
69
|
+
* If an AbortSignal is given and it aborts, only this promise rejects;
|
|
70
|
+
* the pushable itself is not ended.
|
|
43
71
|
*/
|
|
44
72
|
onEmpty(options?: AbortOptions): Promise<void>;
|
|
45
73
|
|
|
46
|
-
/**
|
|
47
|
-
* This property contains the total number of bytes in the queue ready to be read.
|
|
48
|
-
*
|
|
49
|
-
*/
|
|
50
|
-
|
|
74
|
+
/** Total number of bytes buffered (across all lanes). */
|
|
51
75
|
get readableLength(): number;
|
|
52
76
|
|
|
53
77
|
/**
|
|
54
|
-
* Get readable length for specific lane
|
|
55
|
-
* @param lane
|
|
56
|
-
* @returns readable length for the lane
|
|
78
|
+
* Get readable length for a specific lane (bytes) or total when `lane` is undefined.
|
|
57
79
|
*/
|
|
58
80
|
getReadableLength(lane?: number): number;
|
|
59
81
|
}
|
|
60
82
|
|
|
83
|
+
/** How to distribute turns between lanes. */
|
|
84
|
+
export type FairnessMode = "priority" | "wrr";
|
|
85
|
+
|
|
86
|
+
/** What to do when buffer would exceed `maxBufferedBytes`. */
|
|
87
|
+
export type OverflowPolicy = "throw" | "drop-newest";
|
|
88
|
+
|
|
61
89
|
export interface Options {
|
|
62
90
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* passed the error as a parameter.
|
|
91
|
+
* Called after *all* values have been yielded from the iterator (including buffered values).
|
|
92
|
+
* If the iterator is ended with an error it will receive the error.
|
|
66
93
|
*/
|
|
67
94
|
onEnd?(err?: Error): void;
|
|
68
95
|
|
|
69
96
|
/**
|
|
70
|
-
*
|
|
97
|
+
* Number of lanes. Lane 0 is the "fastest"/most preferred lane.
|
|
98
|
+
* Default: 1
|
|
71
99
|
*/
|
|
72
100
|
lanes?: number;
|
|
101
|
+
|
|
73
102
|
/**
|
|
74
|
-
* Optional hook invoked on every successful push with the value and lane
|
|
103
|
+
* Optional hook invoked on every successful push with the value and lane.
|
|
104
|
+
* Useful for metrics/telemetry.
|
|
75
105
|
*/
|
|
76
106
|
onPush?(value: { byteLength: number }, lane: number): void;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Fairness mode:
|
|
110
|
+
* - 'priority': strict priority (original behavior).
|
|
111
|
+
* - 'wrr': weighted round-robin (starvation-free).
|
|
112
|
+
* Default: 'wrr'
|
|
113
|
+
*/
|
|
114
|
+
fairness?: FairnessMode;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Weights per lane if fairness === 'wrr'. Larger weight = more service.
|
|
118
|
+
* Must have length === lanes and each weight >= 1.
|
|
119
|
+
* If omitted, weights are auto-generated from `bias`.
|
|
120
|
+
*/
|
|
121
|
+
weights?: number[];
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Bias factor for auto-generated weights when fairness === 'wrr'.
|
|
125
|
+
* For lanes L, weight[i] = floor(bias^(L-1-i)) with a minimum of 1.
|
|
126
|
+
* Default: 2 (e.g., lanes=4 -> [8,4,2,1])
|
|
127
|
+
*/
|
|
128
|
+
bias?: number;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Optional high-water mark in **bytes** across all lanes.
|
|
132
|
+
* If a `push` would exceed this many buffered bytes:
|
|
133
|
+
* - overflow: 'throw' -> throw an Error (default policy)
|
|
134
|
+
* - overflow: 'drop-newest' -> silently drop this pushed item
|
|
135
|
+
*/
|
|
136
|
+
maxBufferedBytes?: number;
|
|
137
|
+
|
|
138
|
+
/** Overflow policy when `maxBufferedBytes` would be exceeded. Default: 'throw' */
|
|
139
|
+
overflow?: OverflowPolicy;
|
|
77
140
|
}
|
|
78
141
|
|
|
142
|
+
// -----------------------------
|
|
143
|
+
// Internal queue primitives
|
|
144
|
+
// -----------------------------
|
|
145
|
+
|
|
79
146
|
export interface DoneResult {
|
|
80
147
|
done: true;
|
|
81
148
|
}
|
|
@@ -92,94 +159,185 @@ export interface Next<T> {
|
|
|
92
159
|
}
|
|
93
160
|
|
|
94
161
|
/**
|
|
95
|
-
*
|
|
162
|
+
* FIFO that tracks the total readable bytes (`.size`) of queued values.
|
|
96
163
|
*/
|
|
97
|
-
class
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
size: number = 0;
|
|
164
|
+
class ByteFifo<T extends { byteLength: number }> extends GenericFIFO<Next<T>> {
|
|
165
|
+
size = 0;
|
|
166
|
+
|
|
101
167
|
push(val: Next<T>): void {
|
|
102
|
-
if (val.value)
|
|
103
|
-
this.size += val.value.byteLength;
|
|
104
|
-
}
|
|
168
|
+
if (val.value) this.size += val.value.byteLength;
|
|
105
169
|
return super.push(val);
|
|
106
170
|
}
|
|
107
171
|
|
|
108
172
|
shift(): Next<T> | undefined {
|
|
109
173
|
const shifted = super.shift();
|
|
110
|
-
if (shifted?.value)
|
|
111
|
-
this.size -= shifted.value.byteLength;
|
|
112
|
-
}
|
|
174
|
+
if (shifted?.value) this.size -= shifted.value.byteLength;
|
|
113
175
|
return shifted;
|
|
114
176
|
}
|
|
115
177
|
}
|
|
116
178
|
|
|
117
179
|
/**
|
|
118
|
-
* A queue
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* so no lane get really "stuck"
|
|
180
|
+
* A multi-lane queue with configurable fairness.
|
|
181
|
+
* - 'priority': probe lanes in order 0..L-1 each shift.
|
|
182
|
+
* - 'wrr': service lanes according to weights in a repeating schedule.
|
|
122
183
|
*/
|
|
123
|
-
class
|
|
124
|
-
lanes:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
184
|
+
class LaneQueue<T extends { byteLength: number }> {
|
|
185
|
+
public readonly lanes: ByteFifo<T>[];
|
|
186
|
+
private readonly mode: FairnessMode;
|
|
187
|
+
private readonly schedule: number[]; // WRR: repeated lane indices per weight
|
|
188
|
+
private cursor = 0;
|
|
189
|
+
|
|
190
|
+
constructor(init: {
|
|
191
|
+
lanes: number;
|
|
192
|
+
fairness?: FairnessMode;
|
|
193
|
+
weights?: number[];
|
|
194
|
+
bias?: number;
|
|
195
|
+
}) {
|
|
196
|
+
const L = Math.max(1, init.lanes | 0);
|
|
197
|
+
this.mode = init.fairness ?? "wrr";
|
|
198
|
+
this.lanes = Array.from({ length: L }, () => new ByteFifo<T>());
|
|
199
|
+
|
|
200
|
+
if (this.mode === "wrr") {
|
|
201
|
+
const bias = init.bias ?? 2;
|
|
202
|
+
const auto = Array.from({ length: L }, (_, i) =>
|
|
203
|
+
Math.max(1, Math.floor(Math.pow(bias, L - 1 - i))),
|
|
204
|
+
);
|
|
205
|
+
const w = normalizeWeights(init.weights ?? auto, L);
|
|
206
|
+
|
|
207
|
+
// Build a simple round-robin schedule by repeating lanes according to weight.
|
|
208
|
+
this.schedule = [];
|
|
209
|
+
for (let i = 0; i < L; i++) {
|
|
210
|
+
for (let k = 0; k < w[i]; k++) this.schedule.push(i);
|
|
211
|
+
}
|
|
212
|
+
// Edge case: if all weights collapsed to zero (shouldn't), fall back to priority.
|
|
213
|
+
if (this.schedule.length === 0) {
|
|
214
|
+
this.schedule = Array.from({ length: L }, (_, i) => i);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// strict priority
|
|
218
|
+
this.schedule = Array.from({ length: L }, (_, i) => i);
|
|
129
219
|
}
|
|
130
220
|
}
|
|
131
221
|
|
|
132
|
-
get size() {
|
|
222
|
+
get size(): number {
|
|
133
223
|
let sum = 0;
|
|
134
|
-
for (const lane of this.lanes)
|
|
135
|
-
sum += lane.size;
|
|
136
|
-
}
|
|
224
|
+
for (const lane of this.lanes) sum += lane.size;
|
|
137
225
|
return sum;
|
|
138
226
|
}
|
|
139
|
-
|
|
140
|
-
|
|
227
|
+
|
|
228
|
+
/** Enqueue a value into a specific lane. */
|
|
229
|
+
push(val: Next<T>, lane: number): void {
|
|
230
|
+
const idx = clampLane(lane, this.lanes.length);
|
|
231
|
+
this.lanes[idx].push(val);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** True if all lanes are empty. */
|
|
235
|
+
isEmpty(): boolean {
|
|
236
|
+
for (const lane of this.lanes) if (!lane.isEmpty()) return false;
|
|
237
|
+
return true;
|
|
141
238
|
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Dequeue the next value by fairness rules.
|
|
242
|
+
* Ensures progress even if some schedule slots map to empty lanes.
|
|
243
|
+
*/
|
|
142
244
|
shift(): Next<T> | undefined {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
245
|
+
if (this.isEmpty()) return undefined;
|
|
246
|
+
|
|
247
|
+
if (this.mode === "priority") {
|
|
248
|
+
// strict priority: always scan from lane 0
|
|
249
|
+
for (let i = 0; i < this.lanes.length; i++) {
|
|
250
|
+
const item = this.lanes[i].shift();
|
|
251
|
+
if (item) return item;
|
|
149
252
|
}
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// WRR mode: use rotating schedule
|
|
257
|
+
const L = this.schedule.length;
|
|
258
|
+
for (let probes = 0; probes < L; probes++) {
|
|
259
|
+
const laneIdx = this.schedule[this.cursor];
|
|
260
|
+
this.cursor = (this.cursor + 1) % L;
|
|
261
|
+
|
|
262
|
+
const item = this.lanes[laneIdx].shift();
|
|
263
|
+
if (item) return item;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// (very unlikely) nothing was found despite size>0 – linear scan fallback
|
|
267
|
+
for (let i = 0; i < this.lanes.length; i++) {
|
|
268
|
+
const item = this.lanes[i].shift();
|
|
269
|
+
if (item) return item;
|
|
150
270
|
}
|
|
151
271
|
return undefined;
|
|
152
272
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function normalizeWeights(weights: number[], lanes: number): number[] {
|
|
276
|
+
if (weights.length !== lanes) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`weights length (${weights.length}) must equal lanes (${lanes})`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
const w = weights.map((x) => (x && x > 0 ? Math.floor(x) : 0));
|
|
282
|
+
if (w.every((x) => x === 0)) {
|
|
283
|
+
// ensure at least 1 for all lanes to retain progress guarantees
|
|
284
|
+
return Array.from({ length: lanes }, () => 1);
|
|
160
285
|
}
|
|
286
|
+
return w;
|
|
161
287
|
}
|
|
162
288
|
|
|
289
|
+
function clampLane(lane: number, lanes: number): number {
|
|
290
|
+
if (!Number.isFinite(lane)) return 0;
|
|
291
|
+
lane = lane | 0;
|
|
292
|
+
if (lane < 0) return 0;
|
|
293
|
+
if (lane >= lanes) return lanes - 1;
|
|
294
|
+
return lane;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// -----------------------------
|
|
298
|
+
// Factory
|
|
299
|
+
// -----------------------------
|
|
300
|
+
|
|
163
301
|
export function pushableLanes<T extends { byteLength: number } = Uint8Array>(
|
|
164
302
|
options: Options = {},
|
|
165
303
|
): PushableLanes<T> {
|
|
166
304
|
return _pushable<Uint8Array, T, PushableLanes<T>>(options);
|
|
167
305
|
}
|
|
168
306
|
|
|
169
|
-
//
|
|
307
|
+
// -----------------------------
|
|
308
|
+
// Core implementation
|
|
309
|
+
// -----------------------------
|
|
310
|
+
// Based on it-pushable, adapted to multi-lane buffered queues with fairness.
|
|
311
|
+
// Important invariants:
|
|
312
|
+
// - We resolve the internal "drain" promise whenever the buffer *becomes empty*.
|
|
313
|
+
// - After end(err), the iterator finishes; push() becomes a no-op.
|
|
314
|
+
|
|
170
315
|
function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
171
316
|
options?: Options,
|
|
172
317
|
): ReturnType {
|
|
173
318
|
options = options ?? {};
|
|
174
319
|
let onEnd = options.onEnd;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
320
|
+
|
|
321
|
+
// Main buffer: multi-lane with fairness
|
|
322
|
+
let buffer: LaneQueue<PushType> | ByteFifo<PushType> =
|
|
323
|
+
new LaneQueue<PushType>({
|
|
324
|
+
lanes: options.lanes ?? 1,
|
|
325
|
+
fairness: options.fairness ?? "wrr",
|
|
326
|
+
weights: options.weights,
|
|
327
|
+
bias: options.bias ?? 2,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// After end(err) we may swap buffer to a simple ByteFifo to deliver the terminal signal/error.
|
|
331
|
+
const isLaneQueue = (buffer: any): buffer is LaneQueue<PushType> =>
|
|
332
|
+
buffer instanceof LaneQueue;
|
|
333
|
+
|
|
179
334
|
let pushable: any;
|
|
180
335
|
let onNext: ((next: Next<PushType>, lane: number) => ReturnType) | null;
|
|
181
|
-
let ended
|
|
182
|
-
let drain = defer();
|
|
336
|
+
let ended = false;
|
|
337
|
+
let drain = defer<void>();
|
|
338
|
+
|
|
339
|
+
const maxBytes = options.maxBufferedBytes;
|
|
340
|
+
const overflow: OverflowPolicy = options.overflow ?? "throw";
|
|
183
341
|
|
|
184
342
|
const getNext = (): NextResult<ValueType> => {
|
|
185
343
|
const next: Next<PushType> | undefined = buffer.shift();
|
|
@@ -187,11 +345,9 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
|
187
345
|
if (next == null) {
|
|
188
346
|
return { done: true };
|
|
189
347
|
}
|
|
190
|
-
|
|
191
348
|
if (next.error != null) {
|
|
192
349
|
throw next.error;
|
|
193
350
|
}
|
|
194
|
-
|
|
195
351
|
return {
|
|
196
352
|
done: next.done === true,
|
|
197
353
|
// @ts-expect-error if done is false, value will be present
|
|
@@ -204,7 +360,6 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
|
204
360
|
if (!buffer.isEmpty()) {
|
|
205
361
|
return getNext();
|
|
206
362
|
}
|
|
207
|
-
|
|
208
363
|
if (ended) {
|
|
209
364
|
return { done: true };
|
|
210
365
|
}
|
|
@@ -213,23 +368,20 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
|
213
368
|
onNext = (next: Next<PushType>, lane: number) => {
|
|
214
369
|
onNext = null;
|
|
215
370
|
buffer.push(next, lane);
|
|
216
|
-
|
|
217
371
|
try {
|
|
218
372
|
resolve(getNext());
|
|
219
373
|
} catch (err: any) {
|
|
220
374
|
reject(err);
|
|
221
375
|
}
|
|
222
|
-
|
|
223
376
|
return pushable;
|
|
224
377
|
};
|
|
225
378
|
});
|
|
226
379
|
} finally {
|
|
380
|
+
// If buffer is empty after this turn, resolve the drain promise (in a microtask)
|
|
227
381
|
if (buffer.isEmpty()) {
|
|
228
|
-
// settle promise in the microtask queue to give consumers a chance to
|
|
229
|
-
// await after calling .push
|
|
230
382
|
queueMicrotask(() => {
|
|
231
383
|
drain.resolve();
|
|
232
|
-
drain = defer();
|
|
384
|
+
drain = defer<void>();
|
|
233
385
|
});
|
|
234
386
|
}
|
|
235
387
|
}
|
|
@@ -239,46 +391,69 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
|
239
391
|
if (onNext != null) {
|
|
240
392
|
return onNext(next, lane);
|
|
241
393
|
}
|
|
242
|
-
|
|
243
394
|
buffer.push(next, lane);
|
|
244
395
|
return pushable;
|
|
245
396
|
};
|
|
246
397
|
|
|
247
398
|
const bufferError = (err: Error): ReturnType => {
|
|
248
|
-
|
|
249
|
-
|
|
399
|
+
// swap to ByteFifo to deliver a single terminal error
|
|
400
|
+
buffer = new ByteFifo<PushType>();
|
|
250
401
|
if (onNext != null) {
|
|
251
402
|
return onNext({ error: err }, 0);
|
|
252
403
|
}
|
|
253
|
-
|
|
254
404
|
buffer.push({ error: err });
|
|
255
405
|
return pushable;
|
|
256
406
|
};
|
|
257
407
|
|
|
408
|
+
const totalBufferedBytes = (): number => {
|
|
409
|
+
if (isLaneQueue(buffer)) return buffer.size;
|
|
410
|
+
return (buffer as ByteFifo<PushType>).size;
|
|
411
|
+
};
|
|
412
|
+
|
|
258
413
|
const push = (value: PushType, lane: number = 0): ReturnType => {
|
|
259
414
|
if (ended) {
|
|
415
|
+
// Ignore pushes after end() for safety (consistent with it-pushable).
|
|
260
416
|
return pushable;
|
|
261
417
|
}
|
|
262
418
|
|
|
419
|
+
// Simple backpressure: enforce HWM if configured
|
|
420
|
+
if (maxBytes != null && maxBytes > 0) {
|
|
421
|
+
const wouldBe = totalBufferedBytes() + value.byteLength;
|
|
422
|
+
if (wouldBe > maxBytes) {
|
|
423
|
+
if (overflow === "drop-newest") {
|
|
424
|
+
// silently drop this item
|
|
425
|
+
return pushable;
|
|
426
|
+
}
|
|
427
|
+
// default 'throw'
|
|
428
|
+
throw new Error(
|
|
429
|
+
`pushableLanes buffer overflow: ${wouldBe} bytes > maxBufferedBytes=${maxBytes}`,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
263
434
|
const out = bufferNext({ done: false, value }, lane);
|
|
264
|
-
options?.onPush?.(
|
|
435
|
+
options?.onPush?.(
|
|
436
|
+
value,
|
|
437
|
+
clampLane(lane, isLaneQueue(buffer) ? buffer.lanes.length : 1),
|
|
438
|
+
);
|
|
265
439
|
return out;
|
|
266
440
|
};
|
|
441
|
+
|
|
267
442
|
const end = (err?: Error): ReturnType => {
|
|
268
443
|
if (ended) return pushable;
|
|
269
444
|
ended = true;
|
|
270
|
-
|
|
271
445
|
return err != null ? bufferError(err) : bufferNext({ done: true }, 0);
|
|
272
446
|
};
|
|
447
|
+
|
|
273
448
|
const _return = (): DoneResult => {
|
|
274
|
-
|
|
449
|
+
// Ensure prompt termination
|
|
450
|
+
buffer = new ByteFifo<PushType>();
|
|
275
451
|
end();
|
|
276
|
-
|
|
277
452
|
return { done: true };
|
|
278
453
|
};
|
|
454
|
+
|
|
279
455
|
const _throw = (err: Error): DoneResult => {
|
|
280
456
|
end(err);
|
|
281
|
-
|
|
282
457
|
return { done: true };
|
|
283
458
|
};
|
|
284
459
|
|
|
@@ -291,45 +466,41 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
|
291
466
|
throw: _throw,
|
|
292
467
|
push,
|
|
293
468
|
end,
|
|
469
|
+
|
|
294
470
|
get readableLength(): number {
|
|
295
|
-
return
|
|
471
|
+
return totalBufferedBytes();
|
|
296
472
|
},
|
|
297
473
|
|
|
298
474
|
getReadableLength(lane?: number): number {
|
|
299
|
-
if (lane == null)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (buffer instanceof Uint8arrayPriorityQueue) {
|
|
304
|
-
return buffer.lanes[lane].size;
|
|
475
|
+
if (lane == null) return totalBufferedBytes();
|
|
476
|
+
if (isLaneQueue(buffer)) {
|
|
477
|
+
const idx = clampLane(lane, buffer.lanes.length);
|
|
478
|
+
return buffer.lanes[idx].size;
|
|
305
479
|
}
|
|
306
|
-
|
|
480
|
+
// After end/error we swap to a ByteFifo: only "total" makes sense.
|
|
481
|
+
return (buffer as ByteFifo<PushType>).size;
|
|
307
482
|
},
|
|
308
|
-
onEmpty: async (options?: AbortOptions) => {
|
|
309
|
-
const signal = options?.signal;
|
|
310
|
-
signal?.throwIfAborted();
|
|
311
483
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
484
|
+
onEmpty: async (opts?: AbortOptions) => {
|
|
485
|
+
const signal = opts?.signal;
|
|
486
|
+
signal?.throwIfAborted?.();
|
|
487
|
+
|
|
488
|
+
if (buffer.isEmpty()) return;
|
|
315
489
|
|
|
316
490
|
let cancel: Promise<void> | undefined;
|
|
317
491
|
let listener: (() => void) | undefined;
|
|
318
492
|
|
|
319
493
|
if (signal != null) {
|
|
320
|
-
cancel = new Promise((
|
|
321
|
-
listener = () =>
|
|
322
|
-
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
signal.addEventListener("abort", listener);
|
|
494
|
+
cancel = new Promise<void>((_, reject) => {
|
|
495
|
+
listener = () => reject(new AbortError());
|
|
496
|
+
signal.addEventListener("abort", listener!);
|
|
326
497
|
});
|
|
327
498
|
}
|
|
328
499
|
|
|
329
500
|
try {
|
|
330
501
|
await Promise.race([drain.promise, cancel]);
|
|
331
502
|
} finally {
|
|
332
|
-
if (listener != null
|
|
503
|
+
if (listener != null) {
|
|
333
504
|
signal?.removeEventListener("abort", listener);
|
|
334
505
|
}
|
|
335
506
|
}
|
|
@@ -340,6 +511,7 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
|
340
511
|
return pushable;
|
|
341
512
|
}
|
|
342
513
|
|
|
514
|
+
// Wrap with onEnd notifier
|
|
343
515
|
const _pushable = pushable;
|
|
344
516
|
|
|
345
517
|
pushable = {
|
|
@@ -351,39 +523,36 @@ function _pushable<PushType extends Uint8Array, ValueType, ReturnType>(
|
|
|
351
523
|
},
|
|
352
524
|
throw(err: Error) {
|
|
353
525
|
_pushable.throw(err);
|
|
354
|
-
|
|
355
526
|
if (onEnd != null) {
|
|
356
527
|
onEnd(err);
|
|
357
528
|
onEnd = undefined;
|
|
358
529
|
}
|
|
359
|
-
|
|
360
530
|
return { done: true };
|
|
361
531
|
},
|
|
362
532
|
return() {
|
|
363
533
|
_pushable.return();
|
|
364
|
-
|
|
365
534
|
if (onEnd != null) {
|
|
366
535
|
onEnd();
|
|
367
536
|
onEnd = undefined;
|
|
368
537
|
}
|
|
369
|
-
|
|
370
538
|
return { done: true };
|
|
371
539
|
},
|
|
372
540
|
push,
|
|
373
|
-
end(err
|
|
541
|
+
end(err?: Error) {
|
|
374
542
|
_pushable.end(err);
|
|
375
|
-
|
|
376
543
|
if (onEnd != null) {
|
|
377
544
|
onEnd(err);
|
|
378
545
|
onEnd = undefined;
|
|
379
546
|
}
|
|
380
|
-
|
|
381
547
|
return pushable;
|
|
382
548
|
},
|
|
383
549
|
get readableLength() {
|
|
384
550
|
return _pushable.readableLength;
|
|
385
551
|
},
|
|
386
|
-
|
|
552
|
+
getReadableLength(lane?: number) {
|
|
553
|
+
return _pushable.getReadableLength(lane);
|
|
554
|
+
},
|
|
555
|
+
onEmpty(opts?: AbortOptions) {
|
|
387
556
|
return _pushable.onEmpty(opts);
|
|
388
557
|
},
|
|
389
558
|
};
|