@peerbit/stream 4.4.0 → 4.4.1-94a82ff
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 -27
- package/dist/src/pushable-lanes.d.ts.map +1 -1
- package/dist/src/pushable-lanes.js +180 -66
- package/dist/src/pushable-lanes.js.map +1 -1
- package/package.json +94 -94
- package/src/index.ts +108 -11
- package/src/pushable-lanes.ts +281 -127
package/package.json
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
2
|
+
"name": "@peerbit/stream",
|
|
3
|
+
"version": "4.4.1-94a82ff",
|
|
4
|
+
"description": "A building block for direct streaming protocols",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
|
+
"typesVersions": {
|
|
9
|
+
"*": {
|
|
10
|
+
"*": [
|
|
11
|
+
"*",
|
|
12
|
+
"dist/*",
|
|
13
|
+
"dist/src/*",
|
|
14
|
+
"dist/src/*/index"
|
|
15
|
+
],
|
|
16
|
+
"src/*": [
|
|
17
|
+
"*",
|
|
18
|
+
"dist/*",
|
|
19
|
+
"dist/src/*",
|
|
20
|
+
"dist/src/*/index"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"dist",
|
|
27
|
+
"!dist/e2e",
|
|
28
|
+
"!dist/test",
|
|
29
|
+
"!**/*.tsbuildinfo"
|
|
30
|
+
],
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/src/index.d.ts",
|
|
34
|
+
"import": "./dist/src/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"eslintConfig": {
|
|
38
|
+
"extends": "peerbit",
|
|
39
|
+
"parserOptions": {
|
|
40
|
+
"project": true,
|
|
41
|
+
"sourceType": "module"
|
|
42
|
+
},
|
|
43
|
+
"ignorePatterns": [
|
|
44
|
+
"!.aegir.js",
|
|
45
|
+
"test/ts-use",
|
|
46
|
+
"*.d.ts"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"bench": "node --loader ts-node/esm ./benchmark/index.ts",
|
|
54
|
+
"clean": "aegir clean",
|
|
55
|
+
"build": "aegir build --no-bundle",
|
|
56
|
+
"test": "aegir test --target node",
|
|
57
|
+
"lint": "aegir lint"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=16.15.1"
|
|
61
|
+
},
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "git+https://github.com/dao-xyz/peerbit.git"
|
|
65
|
+
},
|
|
66
|
+
"keywords": [
|
|
67
|
+
"peerbit"
|
|
68
|
+
],
|
|
69
|
+
"author": "dao.xyz",
|
|
70
|
+
"license": "MIT",
|
|
71
|
+
"bugs": {
|
|
72
|
+
"url": "https://github.com/dao-xyz/peerbit/issues"
|
|
73
|
+
},
|
|
74
|
+
"homepage": "https://github.com/dao-xyz/peerbit#readme",
|
|
75
|
+
"localMaintainers": [
|
|
76
|
+
"dao.xyz"
|
|
77
|
+
],
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@peerbit/libp2p-test-utils": "2.1.20-94a82ff",
|
|
80
|
+
"@types/yallist": "^4.0.4",
|
|
81
|
+
"@types/fast-fifo": "^1.0.2"
|
|
82
|
+
},
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"p-queue": "^8.0.1",
|
|
85
|
+
"fast-fifo": "^1.3.2",
|
|
86
|
+
"@dao-xyz/borsh": "^5.2.3",
|
|
87
|
+
"@peerbit/cache": "2.1.4-94a82ff",
|
|
88
|
+
"@peerbit/crypto": "2.3.11-94a82ff",
|
|
89
|
+
"@peerbit/stream-interface": "5.2.5-94a82ff",
|
|
90
|
+
"@peerbit/time": "2.2.0-94a82ff",
|
|
91
|
+
"@peerbit/logger": "1.0.4-94a82ff",
|
|
92
|
+
"libp2p": "^2.10.0",
|
|
93
|
+
"yallist": "^4.0.0",
|
|
94
|
+
"abortable-iterator": "^5.0.1"
|
|
95
|
+
}
|
|
96
96
|
}
|
package/src/index.ts
CHANGED
|
@@ -60,6 +60,7 @@ import type {
|
|
|
60
60
|
} from "@peerbit/stream-interface";
|
|
61
61
|
import { AbortError, TimeoutError, delay } from "@peerbit/time";
|
|
62
62
|
import { abortableSource } from "abortable-iterator";
|
|
63
|
+
import { anySignal } from "any-signal";
|
|
63
64
|
import * as lp from "it-length-prefixed";
|
|
64
65
|
import { pipe } from "it-pipe";
|
|
65
66
|
import { type Pushable, pushable } from "it-pushable";
|
|
@@ -130,6 +131,8 @@ const DEFAULT_PRUNED_CONNNECTIONS_TIMEOUT = 30 * 1000;
|
|
|
130
131
|
|
|
131
132
|
const ROUTE_UPDATE_DELAY_FACTOR = 3e4;
|
|
132
133
|
|
|
134
|
+
const DEFAULT_CREATE_OUTBOUND_STREAM_TIMEOUT = 30_000;
|
|
135
|
+
|
|
133
136
|
const getLaneFromPriority = (priority: number) => {
|
|
134
137
|
if (priority > 0) {
|
|
135
138
|
return 0;
|
|
@@ -262,14 +265,31 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
262
265
|
raw,
|
|
263
266
|
).catch((e: any) => {
|
|
264
267
|
candidate.aborted = true;
|
|
268
|
+
try {
|
|
269
|
+
pushableInst.end(e);
|
|
270
|
+
} catch {}
|
|
265
271
|
logError(e as { message: string } as any);
|
|
266
272
|
});
|
|
267
273
|
this.outboundStreams.push(candidate);
|
|
268
274
|
const origAbort = raw.abort?.bind(raw);
|
|
269
275
|
raw.abort = (err?: any) => {
|
|
270
276
|
candidate.aborted = true;
|
|
277
|
+
try {
|
|
278
|
+
pushableInst.end(err);
|
|
279
|
+
} catch {}
|
|
271
280
|
return origAbort?.(err);
|
|
272
281
|
};
|
|
282
|
+
|
|
283
|
+
const origClose = raw.close?.bind(raw);
|
|
284
|
+
if (origClose) {
|
|
285
|
+
raw.close = (...args: any[]) => {
|
|
286
|
+
candidate.aborted = true;
|
|
287
|
+
try {
|
|
288
|
+
pushableInst.end();
|
|
289
|
+
} catch {}
|
|
290
|
+
return origClose(...args);
|
|
291
|
+
};
|
|
292
|
+
}
|
|
273
293
|
return candidate;
|
|
274
294
|
}
|
|
275
295
|
|
|
@@ -310,7 +330,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
310
330
|
* Do we have a connection to write on?
|
|
311
331
|
*/
|
|
312
332
|
get isWritable() {
|
|
313
|
-
return this.outboundStreams.
|
|
333
|
+
return this.outboundStreams.some((c) => !c.aborted);
|
|
314
334
|
}
|
|
315
335
|
|
|
316
336
|
get usedBandwidth() {
|
|
@@ -342,6 +362,12 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
342
362
|
let failures: any[] = [];
|
|
343
363
|
const failed: OutboundCandidate[] = [];
|
|
344
364
|
for (const c of this.outboundStreams) {
|
|
365
|
+
if (c.aborted) {
|
|
366
|
+
failures.push(new Error("aborted"));
|
|
367
|
+
failed.push(c);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
345
371
|
try {
|
|
346
372
|
c.pushable.push(
|
|
347
373
|
payload,
|
|
@@ -373,6 +399,9 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
373
399
|
(c) => !failed.includes(c),
|
|
374
400
|
);
|
|
375
401
|
for (const f of failed) {
|
|
402
|
+
try {
|
|
403
|
+
f.pushable.end(new AbortError("Failed write" as any));
|
|
404
|
+
} catch {}
|
|
376
405
|
try {
|
|
377
406
|
f.raw.abort?.(new AbortError("Failed write" as any));
|
|
378
407
|
} catch {}
|
|
@@ -812,7 +841,7 @@ export abstract class DirectStream<
|
|
|
812
841
|
private routeMaxRetentionPeriod: number;
|
|
813
842
|
|
|
814
843
|
// for sequential creation of outbound streams
|
|
815
|
-
|
|
844
|
+
public outboundInflightQueue: Pushable<{
|
|
816
845
|
connection: Connection;
|
|
817
846
|
peerId: PeerId;
|
|
818
847
|
}>;
|
|
@@ -821,6 +850,7 @@ export abstract class DirectStream<
|
|
|
821
850
|
seekTimeout: number;
|
|
822
851
|
closeController: AbortController;
|
|
823
852
|
session: number;
|
|
853
|
+
_outboundPump: ReturnType<typeof pipe> | undefined;
|
|
824
854
|
|
|
825
855
|
private _ackCallbacks: Map<
|
|
826
856
|
string,
|
|
@@ -937,16 +967,76 @@ export abstract class DirectStream<
|
|
|
937
967
|
this.closeController = new AbortController();
|
|
938
968
|
|
|
939
969
|
this.outboundInflightQueue = pushable({ objectMode: true });
|
|
940
|
-
|
|
970
|
+
|
|
971
|
+
const drainOutbound = async (
|
|
972
|
+
source: AsyncIterable<{ peerId: PeerId; connection: Connection }>,
|
|
973
|
+
) => {
|
|
941
974
|
for await (const { peerId, connection } of source) {
|
|
942
|
-
if (this.stopping || this.started
|
|
943
|
-
|
|
975
|
+
if (this.stopping || !this.started) break; // do not 'return' – finish loop cleanly
|
|
976
|
+
|
|
977
|
+
// Skip closed/closing connections
|
|
978
|
+
if (connection?.timeline?.close != null) {
|
|
979
|
+
logger.debug(
|
|
980
|
+
"skip outbound stream on closed connection %s",
|
|
981
|
+
connection.remoteAddr?.toString(),
|
|
982
|
+
);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
try {
|
|
987
|
+
// Pass an abort + timeout into your stream open so it cannot hang forever
|
|
988
|
+
const attemptSignal = anySignal([
|
|
989
|
+
this.closeController.signal,
|
|
990
|
+
AbortSignal.timeout(DEFAULT_CREATE_OUTBOUND_STREAM_TIMEOUT), // pick a sensible per-attempt cap
|
|
991
|
+
]);
|
|
992
|
+
try {
|
|
993
|
+
await this.createOutboundStream(peerId, connection, {
|
|
994
|
+
signal: attemptSignal,
|
|
995
|
+
});
|
|
996
|
+
} finally {
|
|
997
|
+
attemptSignal.clear?.();
|
|
998
|
+
}
|
|
999
|
+
} catch (e: any) {
|
|
1000
|
+
// Treat common shutdowny errors as transient – do NOT crash the pump
|
|
1001
|
+
const msg = String(e?.message ?? e);
|
|
1002
|
+
if (
|
|
1003
|
+
e?.code === "ERR_STREAM_RESET" ||
|
|
1004
|
+
/unexpected end of input|ECONNRESET|EPIPE|Muxer closed|Premature close/i.test(
|
|
1005
|
+
msg,
|
|
1006
|
+
)
|
|
1007
|
+
) {
|
|
1008
|
+
logger.debug(
|
|
1009
|
+
"createOutboundStream transient failure (%s): %s",
|
|
1010
|
+
connection?.remoteAddr,
|
|
1011
|
+
msg,
|
|
1012
|
+
);
|
|
1013
|
+
} else {
|
|
1014
|
+
logger.warn(
|
|
1015
|
+
"createOutboundStream failed (%s): %o",
|
|
1016
|
+
connection?.remoteAddr,
|
|
1017
|
+
e,
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
// continue to next item
|
|
944
1021
|
}
|
|
945
|
-
await this.createOutboundStream(peerId, connection);
|
|
946
1022
|
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
this._outboundPump = pipe(this.outboundInflightQueue, drainOutbound).catch(
|
|
1026
|
+
(e) => {
|
|
1027
|
+
// Only log if we didn't intentionally abort
|
|
1028
|
+
if (!this.closeController.signal.aborted) {
|
|
1029
|
+
logger.error("outbound inflight pipeline crashed: %o", e);
|
|
1030
|
+
// Optional: restart the pump to self-heal
|
|
1031
|
+
this._outboundPump = pipe(
|
|
1032
|
+
this.outboundInflightQueue,
|
|
1033
|
+
drainOutbound,
|
|
1034
|
+
).catch((err) =>
|
|
1035
|
+
logger.error("outbound pump crashed again: %o", err),
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
);
|
|
950
1040
|
|
|
951
1041
|
this.closeController.signal.addEventListener("abort", () => {
|
|
952
1042
|
this.outboundInflightQueue.return();
|
|
@@ -1123,7 +1213,11 @@ export abstract class DirectStream<
|
|
|
1123
1213
|
await this.outboundInflightQueue.push({ peerId, connection });
|
|
1124
1214
|
}
|
|
1125
1215
|
|
|
1126
|
-
protected async createOutboundStream(
|
|
1216
|
+
protected async createOutboundStream(
|
|
1217
|
+
peerId: PeerId,
|
|
1218
|
+
connection: Connection,
|
|
1219
|
+
opts?: { signal?: AbortSignal },
|
|
1220
|
+
) {
|
|
1127
1221
|
for (const existingStreams of connection.streams) {
|
|
1128
1222
|
if (
|
|
1129
1223
|
existingStreams.protocol &&
|
|
@@ -1150,7 +1244,10 @@ export abstract class DirectStream<
|
|
|
1150
1244
|
// research whether we can do without this so we can push data without beeing able to send
|
|
1151
1245
|
// more info here https://github.com/libp2p/js-libp2p/issues/2321
|
|
1152
1246
|
negotiateFully: true,
|
|
1153
|
-
signal:
|
|
1247
|
+
signal: anySignal([
|
|
1248
|
+
this.closeController.signal,
|
|
1249
|
+
...(opts?.signal ? [opts.signal] : []),
|
|
1250
|
+
]),
|
|
1154
1251
|
});
|
|
1155
1252
|
|
|
1156
1253
|
if (stream.protocol == null) {
|