@peerbit/stream 4.3.10 → 4.4.0
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 +52 -18
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +377 -83
- package/dist/src/index.js.map +1 -1
- package/dist/src/pushable-lanes.d.ts +6 -0
- package/dist/src/pushable-lanes.d.ts.map +1 -1
- package/dist/src/pushable-lanes.js +3 -1
- package/dist/src/pushable-lanes.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +434 -114
- package/src/pushable-lanes.ts +7 -1
package/dist/src/index.js
CHANGED
|
@@ -52,6 +52,25 @@ const getLaneFromPriority = (priority) => {
|
|
|
52
52
|
}
|
|
53
53
|
return 1;
|
|
54
54
|
};
|
|
55
|
+
// Hook for tests to override queued length measurement (peerStreams, default impl)
|
|
56
|
+
export let measureOutboundQueuedBytes = (ps) => {
|
|
57
|
+
const active = ps._getActiveOutboundPushable();
|
|
58
|
+
if (!active)
|
|
59
|
+
return 0;
|
|
60
|
+
// Prefer lane-aware helper if present
|
|
61
|
+
// @ts-ignore - optional test helper
|
|
62
|
+
if (typeof active.getReadableLength === "function") {
|
|
63
|
+
try {
|
|
64
|
+
// lane 0 only
|
|
65
|
+
return active.getReadableLength(0) || 0;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// ignore
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// @ts-ignore fallback for vanilla pushable
|
|
72
|
+
return active.readableLength || 0;
|
|
73
|
+
};
|
|
55
74
|
/**
|
|
56
75
|
* Thin wrapper around a peer's inbound / outbound pubsub streams
|
|
57
76
|
*/
|
|
@@ -59,37 +78,95 @@ export class PeerStreams extends TypedEventEmitter {
|
|
|
59
78
|
peerId;
|
|
60
79
|
publicKey;
|
|
61
80
|
protocol;
|
|
81
|
+
// Removed dedicated outboundStream; first element of outboundStreams[] is active
|
|
62
82
|
/**
|
|
63
|
-
*
|
|
64
|
-
*/
|
|
65
|
-
outboundStream;
|
|
66
|
-
/**
|
|
67
|
-
* Read stream
|
|
83
|
+
* Backwards compatible single inbound references (points to first inbound candidate)
|
|
68
84
|
*/
|
|
69
85
|
inboundStream;
|
|
70
|
-
/**
|
|
71
|
-
* The raw outbound stream, as retrieved from conn.newStream
|
|
72
|
-
*/
|
|
73
|
-
rawOutboundStream;
|
|
74
|
-
/**
|
|
75
|
-
* The raw inbound stream, as retrieved from the callback from libp2p.handle
|
|
76
|
-
*/
|
|
77
86
|
rawInboundStream;
|
|
78
87
|
/**
|
|
79
|
-
*
|
|
88
|
+
* Multiple inbound stream support (more permissive than outbound)
|
|
89
|
+
* We retain concurrent inbound streams to avoid races during migration; inactive ones can later be pruned.
|
|
80
90
|
*/
|
|
81
|
-
|
|
91
|
+
inboundStreams = [];
|
|
92
|
+
_inboundPruneTimer;
|
|
93
|
+
static INBOUND_IDLE_MS = 10_000; // configurable grace for inactivity (made public for tests)
|
|
94
|
+
static MAX_INBOUND_STREAMS = 8; // sensible default to prevent flood
|
|
82
95
|
outboundAbortController;
|
|
83
96
|
closed;
|
|
84
97
|
connId;
|
|
85
98
|
seekedOnce;
|
|
86
99
|
usedBandWidthTracker;
|
|
100
|
+
// Unified outbound streams list (during grace may contain >1; after pruning length==1)
|
|
101
|
+
outboundStreams = [];
|
|
102
|
+
// Public debug exposure of current raw outbound streams (during grace may contain >1)
|
|
103
|
+
get rawOutboundStreams() {
|
|
104
|
+
return this.outboundStreams.map((c) => c.raw);
|
|
105
|
+
}
|
|
106
|
+
_getActiveOutboundPushable() {
|
|
107
|
+
return this.outboundStreams[0]?.pushable;
|
|
108
|
+
}
|
|
109
|
+
_getOutboundCount() {
|
|
110
|
+
return this.outboundStreams.length;
|
|
111
|
+
}
|
|
112
|
+
_getInboundCount() {
|
|
113
|
+
return this.inboundStreams.length;
|
|
114
|
+
}
|
|
115
|
+
_debugInboundStats() {
|
|
116
|
+
return this.inboundStreams.map((c) => ({
|
|
117
|
+
id: c.raw.id,
|
|
118
|
+
created: c.created,
|
|
119
|
+
lastActivity: c.lastActivity,
|
|
120
|
+
bytesReceived: c.bytesReceived,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
_outboundPruneTimer;
|
|
124
|
+
static OUTBOUND_GRACE_MS = 500; // TODO configurable
|
|
125
|
+
_addOutboundCandidate(raw) {
|
|
126
|
+
const existing = this.outboundStreams.find((c) => c.raw === raw);
|
|
127
|
+
if (existing)
|
|
128
|
+
return existing;
|
|
129
|
+
const pushableInst = pushableLanes({
|
|
130
|
+
lanes: 2,
|
|
131
|
+
onPush: (val) => {
|
|
132
|
+
candidate.bytesDelivered += val.length || val.byteLength || 0;
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
const candidate = {
|
|
136
|
+
raw,
|
|
137
|
+
pushable: pushableInst,
|
|
138
|
+
created: Date.now(),
|
|
139
|
+
bytesDelivered: 0,
|
|
140
|
+
aborted: false,
|
|
141
|
+
existing: false,
|
|
142
|
+
};
|
|
143
|
+
pipe(pushableInst, (source) => lp.encode(source, { maxDataLength: MAX_DATA_LENGTH_OUT }), raw).catch((e) => {
|
|
144
|
+
candidate.aborted = true;
|
|
145
|
+
logError(e);
|
|
146
|
+
});
|
|
147
|
+
this.outboundStreams.push(candidate);
|
|
148
|
+
const origAbort = raw.abort?.bind(raw);
|
|
149
|
+
raw.abort = (err) => {
|
|
150
|
+
candidate.aborted = true;
|
|
151
|
+
return origAbort?.(err);
|
|
152
|
+
};
|
|
153
|
+
return candidate;
|
|
154
|
+
}
|
|
155
|
+
_scheduleOutboundPrune(reset = true) {
|
|
156
|
+
if (this.outboundStreams.length <= 1)
|
|
157
|
+
return;
|
|
158
|
+
if (reset && this._outboundPruneTimer) {
|
|
159
|
+
clearTimeout(this._outboundPruneTimer);
|
|
160
|
+
}
|
|
161
|
+
if (!this._outboundPruneTimer) {
|
|
162
|
+
this._outboundPruneTimer = setTimeout(() => this.pruneOutboundCandidates(), PeerStreams.OUTBOUND_GRACE_MS);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
87
165
|
constructor(init) {
|
|
88
166
|
super();
|
|
89
167
|
this.peerId = init.peerId;
|
|
90
168
|
this.publicKey = init.publicKey;
|
|
91
169
|
this.protocol = init.protocol;
|
|
92
|
-
this.inboundAbortController = new AbortController();
|
|
93
170
|
this.outboundAbortController = new AbortController();
|
|
94
171
|
this.closed = false;
|
|
95
172
|
this.connId = init.connId;
|
|
@@ -100,13 +177,13 @@ export class PeerStreams extends TypedEventEmitter {
|
|
|
100
177
|
* Do we have a connection to read from?
|
|
101
178
|
*/
|
|
102
179
|
get isReadable() {
|
|
103
|
-
return
|
|
180
|
+
return this.inboundStreams.length > 0;
|
|
104
181
|
}
|
|
105
182
|
/**
|
|
106
183
|
* Do we have a connection to write on?
|
|
107
184
|
*/
|
|
108
185
|
get isWritable() {
|
|
109
|
-
return
|
|
186
|
+
return this.outboundStreams.length > 0;
|
|
110
187
|
}
|
|
111
188
|
get usedBandwidth() {
|
|
112
189
|
return this.usedBandWidthTracker.value;
|
|
@@ -119,14 +196,56 @@ export class PeerStreams extends TypedEventEmitter {
|
|
|
119
196
|
if (data.length > MAX_DATA_LENGTH_OUT) {
|
|
120
197
|
throw new Error(`Message too large (${data.length * 1e-6}) mb). Needs to be less than ${MAX_DATA_LENGTH_OUT * 1e-6} mb`);
|
|
121
198
|
}
|
|
122
|
-
if (this.
|
|
199
|
+
if (!this.isWritable) {
|
|
123
200
|
logger.error("No writable connection to " + this.peerId.toString());
|
|
124
201
|
throw new Error("No writable connection to " + this.peerId.toString());
|
|
125
202
|
}
|
|
126
203
|
this.usedBandWidthTracker.add(data.byteLength);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
204
|
+
// Write to all current outbound streams (normally 1, but >1 during grace)
|
|
205
|
+
const payload = data instanceof Uint8Array ? data : data.subarray();
|
|
206
|
+
let successes = 0;
|
|
207
|
+
let failures = [];
|
|
208
|
+
const failed = [];
|
|
209
|
+
for (const c of this.outboundStreams) {
|
|
210
|
+
try {
|
|
211
|
+
c.pushable.push(payload, c.pushable.getReadableLength(0) === 0
|
|
212
|
+
? 0
|
|
213
|
+
: getLaneFromPriority(priority));
|
|
214
|
+
successes++;
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
failures.push(e);
|
|
218
|
+
failed.push(c);
|
|
219
|
+
logError(e);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (successes === 0) {
|
|
223
|
+
throw new Error("All outbound writes failed (" +
|
|
224
|
+
failures.map((f) => f?.message).join(", ") +
|
|
225
|
+
")");
|
|
226
|
+
}
|
|
227
|
+
if (failures.length > 0) {
|
|
228
|
+
logger.warn(`Partial outbound write failure: ${failures.length} failed, ${successes} succeeded`);
|
|
229
|
+
// Remove failed streams immediately (best-effort)
|
|
230
|
+
if (failed.length) {
|
|
231
|
+
this.outboundStreams = this.outboundStreams.filter((c) => !failed.includes(c));
|
|
232
|
+
for (const f of failed) {
|
|
233
|
+
try {
|
|
234
|
+
f.raw.abort?.(new AbortError("Failed write"));
|
|
235
|
+
}
|
|
236
|
+
catch { }
|
|
237
|
+
try {
|
|
238
|
+
f.raw.close?.();
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
}
|
|
242
|
+
// If more than one remains schedule prune; else ensure outbound event raised
|
|
243
|
+
if (this.outboundStreams.length > 1)
|
|
244
|
+
this._scheduleOutboundPrune(true);
|
|
245
|
+
else
|
|
246
|
+
this.dispatchEvent(new CustomEvent("stream:outbound"));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
130
249
|
}
|
|
131
250
|
/**
|
|
132
251
|
* Write to the outbound stream, waiting until it becomes writable.
|
|
@@ -181,58 +300,207 @@ export class PeerStreams extends TypedEventEmitter {
|
|
|
181
300
|
* Attach a raw inbound stream and setup a read stream
|
|
182
301
|
*/
|
|
183
302
|
attachInboundStream(stream) {
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
303
|
+
// Support multiple concurrent inbound streams with inactivity pruning.
|
|
304
|
+
// Enforce max inbound streams (drop least recently active)
|
|
305
|
+
if (this.inboundStreams.length >= PeerStreams.MAX_INBOUND_STREAMS) {
|
|
306
|
+
let dropIndex = 0;
|
|
307
|
+
for (let i = 1; i < this.inboundStreams.length; i++) {
|
|
308
|
+
const a = this.inboundStreams[i];
|
|
309
|
+
const b = this.inboundStreams[dropIndex];
|
|
310
|
+
if (a.lastActivity < b.lastActivity ||
|
|
311
|
+
(a.lastActivity === b.lastActivity && a.created < b.created)) {
|
|
312
|
+
dropIndex = i;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const [drop] = this.inboundStreams.splice(dropIndex, 1);
|
|
316
|
+
try {
|
|
317
|
+
drop.abortController.abort();
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
logger.error("Failed to abort inbound stream");
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
drop.raw.close?.();
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
logger.error("Failed to close inbound stream");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const abortController = new AbortController();
|
|
330
|
+
const decoded = pipe(stream, (source) => lp.decode(source, { maxDataLength: MAX_DATA_LENGTH_IN }));
|
|
331
|
+
const iterable = abortableSource(decoded, abortController.signal, {
|
|
190
332
|
returnOnAbort: true,
|
|
191
333
|
onReturnError: (err) => {
|
|
192
334
|
logger.error("Inbound stream error", err?.message);
|
|
193
335
|
},
|
|
194
336
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
this.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
337
|
+
const record = {
|
|
338
|
+
raw: stream,
|
|
339
|
+
iterable,
|
|
340
|
+
abortController,
|
|
341
|
+
created: Date.now(),
|
|
342
|
+
lastActivity: Date.now(),
|
|
343
|
+
bytesReceived: 0,
|
|
344
|
+
};
|
|
345
|
+
this.inboundStreams.push(record);
|
|
346
|
+
this._scheduleInboundPrune();
|
|
347
|
+
// Backwards compatibility: keep first inbound as public properties
|
|
348
|
+
if (this.inboundStreams.length === 1) {
|
|
349
|
+
this.rawInboundStream = stream;
|
|
350
|
+
this.inboundStream = iterable;
|
|
351
|
+
}
|
|
209
352
|
this.dispatchEvent(new CustomEvent("stream:inbound"));
|
|
210
|
-
return
|
|
353
|
+
return record;
|
|
354
|
+
}
|
|
355
|
+
_scheduleInboundPrune() {
|
|
356
|
+
if (this._inboundPruneTimer)
|
|
357
|
+
return; // already scheduled
|
|
358
|
+
this._inboundPruneTimer = setTimeout(() => {
|
|
359
|
+
this._inboundPruneTimer = undefined;
|
|
360
|
+
this._pruneInboundInactive();
|
|
361
|
+
if (this.inboundStreams.length > 1) {
|
|
362
|
+
// schedule again if still multiple
|
|
363
|
+
this._scheduleInboundPrune();
|
|
364
|
+
}
|
|
365
|
+
}, PeerStreams.INBOUND_IDLE_MS);
|
|
366
|
+
}
|
|
367
|
+
_pruneInboundInactive() {
|
|
368
|
+
if (this.inboundStreams.length <= 1)
|
|
369
|
+
return;
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
// Keep at least one (the most recently active)
|
|
372
|
+
this.inboundStreams.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
373
|
+
const keep = this.inboundStreams[0];
|
|
374
|
+
const survivors = [keep];
|
|
375
|
+
for (let i = 1; i < this.inboundStreams.length; i++) {
|
|
376
|
+
const candidate = this.inboundStreams[i];
|
|
377
|
+
if (now - candidate.lastActivity <= PeerStreams.INBOUND_IDLE_MS) {
|
|
378
|
+
survivors.push(candidate);
|
|
379
|
+
continue; // still active
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
candidate.abortController.abort();
|
|
383
|
+
}
|
|
384
|
+
catch { }
|
|
385
|
+
try {
|
|
386
|
+
candidate.raw.close?.();
|
|
387
|
+
}
|
|
388
|
+
catch { }
|
|
389
|
+
}
|
|
390
|
+
this.inboundStreams = survivors;
|
|
391
|
+
// update legacy references if they were pruned
|
|
392
|
+
if (!this.inboundStreams.includes(keep)) {
|
|
393
|
+
this.rawInboundStream = this.inboundStreams[0]?.raw;
|
|
394
|
+
this.inboundStream = this.inboundStreams[0]?.iterable;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
forcePruneInbound() {
|
|
398
|
+
if (this._inboundPruneTimer) {
|
|
399
|
+
clearTimeout(this._inboundPruneTimer);
|
|
400
|
+
this._inboundPruneTimer = undefined;
|
|
401
|
+
}
|
|
402
|
+
this._pruneInboundInactive();
|
|
211
403
|
}
|
|
212
404
|
/**
|
|
213
405
|
* Attach a raw outbound stream and setup a write stream
|
|
214
406
|
*/
|
|
215
407
|
async attachOutboundStream(stream) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
await stream.abort?.(new AbortError("Superseded outbound stream"));
|
|
222
|
-
}
|
|
223
|
-
catch { }
|
|
408
|
+
if (this.outboundStreams[0] && stream.id === this.outboundStreams[0].raw.id)
|
|
409
|
+
return; // duplicate
|
|
410
|
+
this._addOutboundCandidate(stream);
|
|
411
|
+
if (this.outboundStreams.length === 1) {
|
|
412
|
+
this.dispatchEvent(new CustomEvent("stream:outbound"));
|
|
224
413
|
return;
|
|
225
414
|
}
|
|
226
|
-
this.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
415
|
+
this._scheduleOutboundPrune(true);
|
|
416
|
+
}
|
|
417
|
+
pruneOutboundCandidates() {
|
|
418
|
+
try {
|
|
419
|
+
const candidates = this.outboundStreams;
|
|
420
|
+
if (!candidates.length)
|
|
421
|
+
return;
|
|
422
|
+
const now = Date.now();
|
|
423
|
+
const healthy = candidates.filter((c) => !c.aborted && c.bytesDelivered > 0);
|
|
424
|
+
let chosen;
|
|
425
|
+
if (healthy.length === 0) {
|
|
426
|
+
chosen = candidates.reduce((a, b) => b.created > a.created ? b : a);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
let bestScore = -Infinity;
|
|
430
|
+
for (const c of healthy) {
|
|
431
|
+
const age = now - c.created || 1;
|
|
432
|
+
const score = c.bytesDelivered / age;
|
|
433
|
+
if (score > bestScore ||
|
|
434
|
+
(score === bestScore && chosen && c.created > chosen.created)) {
|
|
435
|
+
bestScore = score;
|
|
436
|
+
chosen = c;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (!chosen)
|
|
441
|
+
return;
|
|
442
|
+
for (const c of candidates) {
|
|
443
|
+
if (c === chosen)
|
|
444
|
+
continue; // never abort chosen
|
|
445
|
+
try {
|
|
446
|
+
c.raw.abort?.(new AbortError("Replaced outbound stream"));
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
logger.error("Failed to abort outbound stream");
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
c.pushable.return?.();
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
logger.error("Failed to close outbound pushable");
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
c.raw.close?.();
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
logger.error("Failed to close outbound stream");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
this.outboundStreams = [chosen];
|
|
465
|
+
}
|
|
466
|
+
catch (e) {
|
|
467
|
+
logger.error("Error promoting outbound candidate: " + e?.message);
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
this.dispatchEvent(new CustomEvent("stream:outbound"));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
forcePruneOutbound() {
|
|
474
|
+
if (this._outboundPruneTimer) {
|
|
475
|
+
clearTimeout(this._outboundPruneTimer);
|
|
476
|
+
this._outboundPruneTimer = undefined;
|
|
477
|
+
}
|
|
478
|
+
this.pruneOutboundCandidates();
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Internal helper to perform the actual outbound replacement & piping.
|
|
482
|
+
*/
|
|
483
|
+
// _replaceOutboundStream removed (legacy path)
|
|
484
|
+
// Debug/testing helper: list active outbound raw stream ids
|
|
485
|
+
_debugActiveOutboundIds() {
|
|
486
|
+
if (this.outboundStreams.length) {
|
|
487
|
+
return this.outboundStreams.map((c) => c.raw.id);
|
|
488
|
+
}
|
|
489
|
+
return this.outboundStreams.map((c) => c.raw.id);
|
|
490
|
+
}
|
|
491
|
+
_debugOutboundStats() {
|
|
492
|
+
if (this.outboundStreams.length) {
|
|
493
|
+
return this.outboundStreams.map((c) => ({
|
|
494
|
+
id: c.raw.id,
|
|
495
|
+
bytes: c.bytesDelivered,
|
|
496
|
+
aborted: !!c.aborted,
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
return this.outboundStreams.map((c) => ({
|
|
500
|
+
id: c.raw.id,
|
|
501
|
+
bytes: c.bytesDelivered,
|
|
502
|
+
aborted: !!c.aborted,
|
|
503
|
+
}));
|
|
236
504
|
}
|
|
237
505
|
/**
|
|
238
506
|
* Closes the open connection to peer
|
|
@@ -242,23 +510,47 @@ export class PeerStreams extends TypedEventEmitter {
|
|
|
242
510
|
return;
|
|
243
511
|
}
|
|
244
512
|
this.closed = true;
|
|
513
|
+
if (this._outboundPruneTimer) {
|
|
514
|
+
clearTimeout(this._outboundPruneTimer);
|
|
515
|
+
this._outboundPruneTimer = undefined;
|
|
516
|
+
}
|
|
245
517
|
// End the outbound stream
|
|
246
|
-
if (this.
|
|
247
|
-
|
|
248
|
-
|
|
518
|
+
if (this.outboundStreams.length) {
|
|
519
|
+
for (const c of this.outboundStreams) {
|
|
520
|
+
try {
|
|
521
|
+
await c.pushable.return?.();
|
|
522
|
+
}
|
|
523
|
+
catch { }
|
|
524
|
+
try {
|
|
525
|
+
c.raw.abort?.(new AbortError("Closed"));
|
|
526
|
+
}
|
|
527
|
+
catch { }
|
|
528
|
+
}
|
|
249
529
|
this.outboundAbortController.abort();
|
|
250
530
|
}
|
|
251
|
-
// End
|
|
252
|
-
if (this.
|
|
253
|
-
this.
|
|
254
|
-
|
|
531
|
+
// End inbound streams
|
|
532
|
+
if (this.inboundStreams.length) {
|
|
533
|
+
for (const inbound of this.inboundStreams) {
|
|
534
|
+
try {
|
|
535
|
+
inbound.abortController.abort();
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
logger.error("Failed to abort inbound stream");
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
await inbound.raw.close?.();
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
logger.error("Failed to close inbound stream");
|
|
545
|
+
}
|
|
546
|
+
}
|
|
255
547
|
}
|
|
256
548
|
this.usedBandWidthTracker.stop();
|
|
257
549
|
this.dispatchEvent(new CustomEvent("close"));
|
|
258
|
-
this.
|
|
259
|
-
this.outboundStream = undefined;
|
|
550
|
+
this.outboundStreams = [];
|
|
260
551
|
this.rawInboundStream = undefined;
|
|
261
552
|
this.inboundStream = undefined;
|
|
553
|
+
this.inboundStreams = [];
|
|
262
554
|
}
|
|
263
555
|
}
|
|
264
556
|
export class DirectStream extends TypedEventEmitter {
|
|
@@ -304,12 +596,16 @@ export class DirectStream extends TypedEventEmitter {
|
|
|
304
596
|
constructor(components, multicodecs, options) {
|
|
305
597
|
super();
|
|
306
598
|
this.components = components;
|
|
307
|
-
const { canRelayMessage = true, messageProcessingConcurrency = 10, maxInboundStreams, maxOutboundStreams, connectionManager, routeSeekInterval = ROUTE_UPDATE_DELAY_FACTOR, seekTimeout = SEEK_DELIVERY_TIMEOUT, routeMaxRetentionPeriod = ROUTE_MAX_RETANTION_PERIOD, } = options || {};
|
|
599
|
+
const { canRelayMessage = true, messageProcessingConcurrency = 10, maxInboundStreams, maxOutboundStreams, connectionManager, routeSeekInterval = ROUTE_UPDATE_DELAY_FACTOR, seekTimeout = SEEK_DELIVERY_TIMEOUT, routeMaxRetentionPeriod = ROUTE_MAX_RETANTION_PERIOD, inboundIdleTimeout, } = options || {};
|
|
308
600
|
const signKey = getKeypairFromPrivateKey(components.privateKey);
|
|
309
601
|
this.seekTimeout = seekTimeout;
|
|
310
602
|
this.sign = signKey.sign.bind(signKey);
|
|
311
603
|
this.peerId = components.peerId;
|
|
312
604
|
this.publicKey = signKey.publicKey;
|
|
605
|
+
if (inboundIdleTimeout != null)
|
|
606
|
+
PeerStreams.INBOUND_IDLE_MS = inboundIdleTimeout;
|
|
607
|
+
if (maxInboundStreams != null)
|
|
608
|
+
PeerStreams.MAX_INBOUND_STREAMS = maxInboundStreams;
|
|
313
609
|
this.publicKeyHash = signKey.publicKey.hashcode();
|
|
314
610
|
this.multicodecs = multicodecs;
|
|
315
611
|
this.started = false;
|
|
@@ -511,8 +807,8 @@ export class DirectStream extends TypedEventEmitter {
|
|
|
511
807
|
}
|
|
512
808
|
const peer = this.addPeer(peerId, publicKey, stream.protocol, connection.id);
|
|
513
809
|
// handle inbound
|
|
514
|
-
const
|
|
515
|
-
this.processMessages(peer.publicKey,
|
|
810
|
+
const inboundRecord = peer.attachInboundStream(stream);
|
|
811
|
+
this.processMessages(peer.publicKey, inboundRecord, peer).catch(logError);
|
|
516
812
|
// try to create outbound stream
|
|
517
813
|
await this.outboundInflightQueue.push({ peerId, connection });
|
|
518
814
|
}
|
|
@@ -736,15 +1032,14 @@ export class DirectStream extends TypedEventEmitter {
|
|
|
736
1032
|
/**
|
|
737
1033
|
* Responsible for processing each RPC message received by other peers.
|
|
738
1034
|
*/
|
|
739
|
-
async processMessages(peerId,
|
|
1035
|
+
async processMessages(peerId, record, peerStreams) {
|
|
740
1036
|
try {
|
|
741
|
-
await
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
});
|
|
1037
|
+
for await (const data of record.iterable) {
|
|
1038
|
+
const now = Date.now();
|
|
1039
|
+
record.lastActivity = now;
|
|
1040
|
+
record.bytesReceived += data.length || data.byteLength || 0;
|
|
1041
|
+
this.processRpc(peerId, peerStreams, data).catch((e) => logError(e));
|
|
1042
|
+
}
|
|
748
1043
|
}
|
|
749
1044
|
catch (err) {
|
|
750
1045
|
if (err?.code === "ERR_STREAM_RESET") {
|
|
@@ -1555,9 +1850,8 @@ export class DirectStream extends TypedEventEmitter {
|
|
|
1555
1850
|
}
|
|
1556
1851
|
getQueuedBytes() {
|
|
1557
1852
|
let sum = 0;
|
|
1558
|
-
for (const
|
|
1559
|
-
|
|
1560
|
-
sum += out ? out.readableLength : 0;
|
|
1853
|
+
for (const [_k, ps] of this.peers) {
|
|
1854
|
+
sum += measureOutboundQueuedBytes(ps); // cast to access hook
|
|
1561
1855
|
}
|
|
1562
1856
|
return sum;
|
|
1563
1857
|
}
|