@peerbit/stream 5.2.1 → 5.2.2
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 +15 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +204 -58
- package/dist/src/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +220 -83
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ConnectionClosedError,
|
|
2
3
|
MuxerClosedError,
|
|
3
4
|
StreamResetError,
|
|
4
5
|
TypedEventEmitter,
|
|
@@ -268,6 +269,7 @@ const DEFAULT_OUTBOUND_QUEUE_RESERVED_PRIORITY_BYTES = 1024 * 1024;
|
|
|
268
269
|
const DEFAULT_PRUNE_CONNECTIONS_INTERVAL = 2e4;
|
|
269
270
|
const DEFAULT_MIN_CONNECTIONS = 2;
|
|
270
271
|
const DEFAULT_MAX_CONNECTIONS = 300;
|
|
272
|
+
const MAX_RETIRED_PEER_STREAMS = 256;
|
|
271
273
|
|
|
272
274
|
const DEFAULT_PRUNED_CONNNECTIONS_TIMEOUT = 30 * 1000;
|
|
273
275
|
|
|
@@ -387,6 +389,11 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
387
389
|
private outboundAbortController: AbortController;
|
|
388
390
|
|
|
389
391
|
private closed: boolean;
|
|
392
|
+
private closePromise?: Promise<void>;
|
|
393
|
+
/** Closing is irreversible, even while asynchronous cleanup is pending. */
|
|
394
|
+
public get isClosed(): boolean {
|
|
395
|
+
return this.closed;
|
|
396
|
+
}
|
|
390
397
|
|
|
391
398
|
public connId: string;
|
|
392
399
|
|
|
@@ -800,6 +807,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
800
807
|
* Attach a raw inbound stream and setup a read stream
|
|
801
808
|
*/
|
|
802
809
|
attachInboundStream(stream: Stream): InboundStreamRecord {
|
|
810
|
+
this.assertOpenForAttachment(stream);
|
|
803
811
|
// Support multiple concurrent inbound streams with inactivity pruning.
|
|
804
812
|
// Enforce max inbound streams (drop least recently active)
|
|
805
813
|
if (this.inboundStreams.length >= PeerStreams.MAX_INBOUND_STREAMS) {
|
|
@@ -852,9 +860,10 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
852
860
|
}
|
|
853
861
|
|
|
854
862
|
private _scheduleInboundPrune() {
|
|
855
|
-
if (this._inboundPruneTimer) return;
|
|
863
|
+
if (this.closed || this._inboundPruneTimer) return;
|
|
856
864
|
this._inboundPruneTimer = setTimeout(() => {
|
|
857
865
|
this._inboundPruneTimer = undefined;
|
|
866
|
+
if (this.closed) return;
|
|
858
867
|
this._pruneInboundInactive();
|
|
859
868
|
if (this.inboundStreams.length > 1) {
|
|
860
869
|
// schedule again if still multiple
|
|
@@ -864,7 +873,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
864
873
|
}
|
|
865
874
|
|
|
866
875
|
private _pruneInboundInactive() {
|
|
867
|
-
if (this.inboundStreams.length <= 1) return;
|
|
876
|
+
if (this.closed || this.inboundStreams.length <= 1) return;
|
|
868
877
|
const now = Date.now();
|
|
869
878
|
// Keep at least one (the most recently active)
|
|
870
879
|
this.inboundStreams.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
@@ -932,6 +941,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
932
941
|
*/
|
|
933
942
|
|
|
934
943
|
async attachOutboundStream(stream: Stream) {
|
|
944
|
+
this.assertOpenForAttachment(stream);
|
|
935
945
|
if (this.outboundStreams.some((candidate) => candidate.raw === stream)) {
|
|
936
946
|
return; // duplicate
|
|
937
947
|
}
|
|
@@ -943,6 +953,16 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
943
953
|
this._scheduleOutboundPrune(true);
|
|
944
954
|
}
|
|
945
955
|
|
|
956
|
+
private assertOpenForAttachment(stream: Stream) {
|
|
957
|
+
if (!this.closed) return;
|
|
958
|
+
const error = new AbortError("Closed");
|
|
959
|
+
try {
|
|
960
|
+
stream.abort?.(error);
|
|
961
|
+
} catch {}
|
|
962
|
+
closeRawStreamBestEffort(stream);
|
|
963
|
+
throw error;
|
|
964
|
+
}
|
|
965
|
+
|
|
946
966
|
private pruneOutboundCandidates() {
|
|
947
967
|
try {
|
|
948
968
|
const candidates = this.outboundStreams;
|
|
@@ -1036,17 +1056,27 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
1036
1056
|
/**
|
|
1037
1057
|
* Closes the open connection to peer
|
|
1038
1058
|
*/
|
|
1039
|
-
|
|
1040
|
-
if (this.
|
|
1041
|
-
|
|
1042
|
-
|
|
1059
|
+
close(): Promise<void> {
|
|
1060
|
+
if (this.closePromise) return this.closePromise;
|
|
1061
|
+
const closing = pDefer<void>();
|
|
1062
|
+
// Publish the promise before callbacks can re-enter close().
|
|
1063
|
+
this.closePromise = closing.promise;
|
|
1043
1064
|
|
|
1044
1065
|
this.closed = true;
|
|
1066
|
+
// Cancel inbound maintenance before awaiting outbound shutdown.
|
|
1067
|
+
if (this._inboundPruneTimer) {
|
|
1068
|
+
clearTimeout(this._inboundPruneTimer);
|
|
1069
|
+
this._inboundPruneTimer = undefined;
|
|
1070
|
+
}
|
|
1045
1071
|
if (this._outboundPruneTimer) {
|
|
1046
1072
|
clearTimeout(this._outboundPruneTimer);
|
|
1047
1073
|
this._outboundPruneTimer = undefined;
|
|
1048
1074
|
}
|
|
1075
|
+
void this.closeImpl().then(closing.resolve, closing.reject);
|
|
1076
|
+
return this.closePromise;
|
|
1077
|
+
}
|
|
1049
1078
|
|
|
1079
|
+
private async closeImpl(): Promise<void> {
|
|
1050
1080
|
// End the outbound stream
|
|
1051
1081
|
if (this.outboundStreams.length) {
|
|
1052
1082
|
for (const c of this.outboundStreams) {
|
|
@@ -1060,29 +1090,29 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
1060
1090
|
this.outboundAbortController.abort();
|
|
1061
1091
|
}
|
|
1062
1092
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1093
|
+
// End inbound streams
|
|
1094
|
+
if (this.inboundStreams.length) {
|
|
1095
|
+
for (const inbound of this.inboundStreams) {
|
|
1096
|
+
try {
|
|
1097
|
+
inbound.abortController.abort();
|
|
1098
|
+
} catch {
|
|
1099
|
+
logger.error("Failed to abort inbound stream");
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
// Best-effort shutdown: on some transports (notably websockets),
|
|
1103
|
+
// awaiting a graceful close can hang indefinitely if the remote is
|
|
1104
|
+
// concurrently stopping. Abort immediately and do not await close.
|
|
1066
1105
|
try {
|
|
1067
|
-
inbound.
|
|
1106
|
+
inbound.raw.abort?.(new AbortError("Closed"));
|
|
1068
1107
|
} catch {
|
|
1069
|
-
|
|
1070
|
-
}
|
|
1071
|
-
try {
|
|
1072
|
-
// Best-effort shutdown: on some transports (notably websockets),
|
|
1073
|
-
// awaiting a graceful close can hang indefinitely if the remote is
|
|
1074
|
-
// concurrently stopping. Abort immediately and do not await close.
|
|
1075
|
-
try {
|
|
1076
|
-
inbound.raw.abort?.(new AbortError("Closed"));
|
|
1077
|
-
} catch {
|
|
1078
|
-
// ignore
|
|
1079
|
-
}
|
|
1080
|
-
closeRawStreamBestEffort(inbound.raw);
|
|
1081
|
-
} catch {
|
|
1082
|
-
logger.error("Failed to close inbound stream");
|
|
1108
|
+
// ignore
|
|
1083
1109
|
}
|
|
1110
|
+
closeRawStreamBestEffort(inbound.raw);
|
|
1111
|
+
} catch {
|
|
1112
|
+
logger.error("Failed to close inbound stream");
|
|
1084
1113
|
}
|
|
1085
1114
|
}
|
|
1115
|
+
}
|
|
1086
1116
|
|
|
1087
1117
|
this.usedBandWidthTracker.stop();
|
|
1088
1118
|
|
|
@@ -1251,6 +1281,8 @@ export abstract class DirectStream<
|
|
|
1251
1281
|
* Map of peer streams
|
|
1252
1282
|
*/
|
|
1253
1283
|
public peers: Map<string, PeerStreams>;
|
|
1284
|
+
// Replacements must not orphan teardown that beforeStop still needs to drain.
|
|
1285
|
+
private readonly retiredPeerStreams = new Set<PeerStreams>();
|
|
1254
1286
|
public peerKeyHashToPublicKey: Map<string, PublicSignKey>;
|
|
1255
1287
|
public routes: RoutesLike;
|
|
1256
1288
|
/**
|
|
@@ -1277,6 +1309,7 @@ export abstract class DirectStream<
|
|
|
1277
1309
|
private pruneToLimitsInFlight?: Promise<void>;
|
|
1278
1310
|
private _startInFlight?: Promise<void>;
|
|
1279
1311
|
private _stopInFlight?: Promise<void>;
|
|
1312
|
+
private _networkStopPromise?: Promise<void>;
|
|
1280
1313
|
private routeMaxRetentionPeriod: number;
|
|
1281
1314
|
private routeCacheMaxFromEntries?: number;
|
|
1282
1315
|
private routeCacheMaxTargetsPerFrom?: number;
|
|
@@ -1557,10 +1590,11 @@ export abstract class DirectStream<
|
|
|
1557
1590
|
|
|
1558
1591
|
async start() {
|
|
1559
1592
|
// Do not queue a restart behind teardown; callers can retry after stop resolves.
|
|
1560
|
-
if (this._stopInFlight) return;
|
|
1593
|
+
if (this._stopInFlight || this.stopping) return;
|
|
1561
1594
|
if (this.started) return;
|
|
1562
1595
|
if (this._startInFlight) return this._startInFlight;
|
|
1563
1596
|
this.stopping = false;
|
|
1597
|
+
this._networkStopPromise = undefined;
|
|
1564
1598
|
this._startInFlight = this._startImpl().finally(() => {
|
|
1565
1599
|
this._startInFlight = undefined;
|
|
1566
1600
|
});
|
|
@@ -1774,12 +1808,12 @@ export abstract class DirectStream<
|
|
|
1774
1808
|
}
|
|
1775
1809
|
if (this.connectionManagerOptions.pruner) {
|
|
1776
1810
|
const pruneConnectionsLoop = () => {
|
|
1777
|
-
if (!this.connectionManagerOptions.pruner) {
|
|
1811
|
+
if (this.stopping || !this.connectionManagerOptions.pruner) {
|
|
1778
1812
|
return;
|
|
1779
1813
|
}
|
|
1780
1814
|
this.pruneConnectionsTimeout = setTimeout(() => {
|
|
1781
1815
|
this.maybePruneConnections().finally(() => {
|
|
1782
|
-
if (!this.started) {
|
|
1816
|
+
if (this.stopping || !this.started) {
|
|
1783
1817
|
return;
|
|
1784
1818
|
}
|
|
1785
1819
|
pruneConnectionsLoop();
|
|
@@ -1790,12 +1824,24 @@ export abstract class DirectStream<
|
|
|
1790
1824
|
}
|
|
1791
1825
|
}
|
|
1792
1826
|
|
|
1827
|
+
/**
|
|
1828
|
+
* Finish network teardown while libp2p still owns open transport connections.
|
|
1829
|
+
* Subclass resource cleanup remains in the normal stop phase.
|
|
1830
|
+
*/
|
|
1831
|
+
beforeStop(): Promise<void> {
|
|
1832
|
+
if (this._networkStopPromise) return this._networkStopPromise;
|
|
1833
|
+
if (!this.started && !this._startInFlight) return Promise.resolve();
|
|
1834
|
+
return this.stopNetwork();
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1793
1837
|
/**
|
|
1794
1838
|
* Unregister the pubsub protocol and the streams with other peers will be closed.
|
|
1795
1839
|
*/
|
|
1796
1840
|
stop(): Promise<void> {
|
|
1797
1841
|
if (this._stopInFlight) return this._stopInFlight;
|
|
1798
|
-
if (!this.started && !this._startInFlight
|
|
1842
|
+
if (!this.started && !this._startInFlight && !this._networkStopPromise) {
|
|
1843
|
+
return Promise.resolve();
|
|
1844
|
+
}
|
|
1799
1845
|
this.stopping = true;
|
|
1800
1846
|
const starting = this._startInFlight;
|
|
1801
1847
|
this._stopInFlight = this._stopAfterStart(starting).finally(() => {
|
|
@@ -1817,9 +1863,10 @@ export abstract class DirectStream<
|
|
|
1817
1863
|
|
|
1818
1864
|
let stopFailed = false;
|
|
1819
1865
|
let stopFailure: unknown;
|
|
1820
|
-
if (this.started) {
|
|
1866
|
+
if (this.started || this._networkStopPromise) {
|
|
1821
1867
|
try {
|
|
1822
|
-
await this._stopImpl();
|
|
1868
|
+
if (this.started) await this._stopImpl();
|
|
1869
|
+
else await this._networkStopPromise;
|
|
1823
1870
|
} catch (error) {
|
|
1824
1871
|
stopFailed = true;
|
|
1825
1872
|
stopFailure = error;
|
|
@@ -1836,9 +1883,23 @@ export abstract class DirectStream<
|
|
|
1836
1883
|
if (startFailed) throw startFailure;
|
|
1837
1884
|
}
|
|
1838
1885
|
|
|
1839
|
-
private
|
|
1840
|
-
|
|
1841
|
-
|
|
1886
|
+
private stopNetwork(): Promise<void> {
|
|
1887
|
+
if (this._networkStopPromise) return this._networkStopPromise;
|
|
1888
|
+
this.stopping = true;
|
|
1889
|
+
this._networkStopPromise = this.stopNetworkAfterStart(
|
|
1890
|
+
this._startInFlight,
|
|
1891
|
+
).finally(() => {
|
|
1892
|
+
// Startup can fail before marking the service started. In that case no
|
|
1893
|
+
// normal stop is required to make a subsequent start possible.
|
|
1894
|
+
if (!this.started && !this._stopInFlight) this.stopping = false;
|
|
1895
|
+
});
|
|
1896
|
+
return this._networkStopPromise;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
private async stopNetworkAfterStart(starting?: Promise<void>): Promise<void> {
|
|
1900
|
+
// A failed startup may still have installed handlers or opened streams.
|
|
1901
|
+
// Its caller observes the startup error; teardown must still drain them.
|
|
1902
|
+
await starting?.catch(() => {});
|
|
1842
1903
|
|
|
1843
1904
|
clearTimeout(this.pruneConnectionsTimeout);
|
|
1844
1905
|
try {
|
|
@@ -1873,20 +1934,34 @@ export abstract class DirectStream<
|
|
|
1873
1934
|
);
|
|
1874
1935
|
}
|
|
1875
1936
|
|
|
1876
|
-
|
|
1877
|
-
this.
|
|
1878
|
-
this.
|
|
1879
|
-
this.
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1937
|
+
this.outboundInflightQueue?.end();
|
|
1938
|
+
this.closeController?.abort();
|
|
1939
|
+
for (const timer of this.healthChecks.values()) clearTimeout(timer);
|
|
1940
|
+
this.healthChecks.clear();
|
|
1941
|
+
// A stream open may settle after abort and still need to dispose its raw
|
|
1942
|
+
// stream. Keep that work ahead of connection-manager shutdown as well.
|
|
1943
|
+
await this._outboundPump;
|
|
1944
|
+
this._outboundPump = undefined;
|
|
1945
|
+
|
|
1946
|
+
const closes = await Promise.allSettled(
|
|
1947
|
+
[...this.peers.values(), ...this.retiredPeerStreams].map((peer) =>
|
|
1948
|
+
peer.close(),
|
|
1949
|
+
),
|
|
1950
|
+
);
|
|
1951
|
+
const failures = closes.flatMap((result) =>
|
|
1952
|
+
result.status === "rejected" ? [result.reason] : [],
|
|
1953
|
+
);
|
|
1954
|
+
if (failures.length) {
|
|
1955
|
+
throw new AggregateError(failures, "Peer stream teardown failed");
|
|
1884
1956
|
}
|
|
1957
|
+
}
|
|
1885
1958
|
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
this.
|
|
1959
|
+
private async _stopImpl(): Promise<void> {
|
|
1960
|
+
const sharedState = this.sharedRoutingState;
|
|
1961
|
+
const sharedKey = this.sharedRoutingKey;
|
|
1962
|
+
this.started = false;
|
|
1963
|
+
logger.trace("stopping");
|
|
1964
|
+
await this.stopNetwork();
|
|
1890
1965
|
this.prunedConnectionsCache?.clear();
|
|
1891
1966
|
|
|
1892
1967
|
this.queue.clear();
|
|
@@ -1939,7 +2014,8 @@ export abstract class DirectStream<
|
|
|
1939
2014
|
*/
|
|
1940
2015
|
|
|
1941
2016
|
protected async _onIncomingStream(stream: Stream, connection: Connection) {
|
|
1942
|
-
if (!this.isStarted()) {
|
|
2017
|
+
if (this.stopping || !this.isStarted()) {
|
|
2018
|
+
closeRawStreamBestEffort(stream);
|
|
1943
2019
|
return;
|
|
1944
2020
|
}
|
|
1945
2021
|
const peerId = connection.remotePeer;
|
|
@@ -1951,21 +2027,24 @@ export abstract class DirectStream<
|
|
|
1951
2027
|
const publicKey = getPublicKeyFromPeerId(peerId);
|
|
1952
2028
|
|
|
1953
2029
|
if (this.prunedConnectionsCache?.has(publicKey.hashcode())) {
|
|
1954
|
-
|
|
2030
|
+
// Reject immediately: graceful transport shutdown can race other
|
|
2031
|
+
// incoming protocol negotiations that still need to reset their streams.
|
|
2032
|
+
connection.abort(new AbortError("Connection was pruned"));
|
|
1955
2033
|
await this.components.peerStore.delete(peerId);
|
|
1956
2034
|
return;
|
|
1957
2035
|
}
|
|
1958
2036
|
|
|
1959
|
-
|
|
1960
|
-
peerId,
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2037
|
+
try {
|
|
2038
|
+
const peer = this.addPeer(peerId, publicKey, stream.protocol, connection.id);
|
|
2039
|
+
const inboundRecord = peer.attachInboundStream(stream);
|
|
2040
|
+
this.processMessages(peer.publicKey, inboundRecord, peer).catch(logError);
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
try {
|
|
2043
|
+
stream.abort(error as Error);
|
|
2044
|
+
} catch {}
|
|
2045
|
+
closeRawStreamBestEffort(stream);
|
|
2046
|
+
throw error;
|
|
2047
|
+
}
|
|
1969
2048
|
|
|
1970
2049
|
// try to create outbound stream
|
|
1971
2050
|
await this.outboundInflightQueue.push({ peerId, connection });
|
|
@@ -1992,7 +2071,7 @@ export abstract class DirectStream<
|
|
|
1992
2071
|
const peerKey = getPublicKeyFromPeerId(peerId);
|
|
1993
2072
|
while (tries <= 3) {
|
|
1994
2073
|
tries++;
|
|
1995
|
-
if (!this.started) {
|
|
2074
|
+
if (this.stopping || !this.started) {
|
|
1996
2075
|
return;
|
|
1997
2076
|
}
|
|
1998
2077
|
|
|
@@ -2018,7 +2097,7 @@ export abstract class DirectStream<
|
|
|
2018
2097
|
return;
|
|
2019
2098
|
}
|
|
2020
2099
|
|
|
2021
|
-
if (!this.started) {
|
|
2100
|
+
if (this.stopping || !this.started) {
|
|
2022
2101
|
// we closed before we could create the stream
|
|
2023
2102
|
stream.abort(new Error("Closed"));
|
|
2024
2103
|
return;
|
|
@@ -2026,6 +2105,12 @@ export abstract class DirectStream<
|
|
|
2026
2105
|
peer = this.addPeer(peerId, peerKey, stream.protocol!, connection.id); // TODO types
|
|
2027
2106
|
await peer.attachOutboundStream(stream);
|
|
2028
2107
|
} catch (error: any) {
|
|
2108
|
+
if (stream) {
|
|
2109
|
+
try {
|
|
2110
|
+
stream.abort(error);
|
|
2111
|
+
} catch {}
|
|
2112
|
+
closeRawStreamBestEffort(stream);
|
|
2113
|
+
}
|
|
2029
2114
|
if (error.code === "ERR_UNSUPPORTED_PROTOCOL") {
|
|
2030
2115
|
await delay(100);
|
|
2031
2116
|
continue; // Retry
|
|
@@ -2036,14 +2121,25 @@ export abstract class DirectStream<
|
|
|
2036
2121
|
continue; // Retry
|
|
2037
2122
|
}
|
|
2038
2123
|
|
|
2124
|
+
if (connection.status !== "open") return;
|
|
2039
2125
|
if (
|
|
2040
|
-
connection.status !== "open" ||
|
|
2041
2126
|
error?.message === "Muxer already closed" ||
|
|
2042
|
-
error
|
|
2043
|
-
error instanceof StreamResetError ||
|
|
2127
|
+
error instanceof ConnectionClosedError ||
|
|
2044
2128
|
error instanceof MuxerClosedError
|
|
2045
2129
|
) {
|
|
2046
|
-
|
|
2130
|
+
// A closed muxer cannot open any protocol, even if the transport
|
|
2131
|
+
// still reports open. Dispose it so a later dial can reconnect.
|
|
2132
|
+
connection.abort(error);
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
if (
|
|
2136
|
+
error.code === "ERR_STREAM_RESET" ||
|
|
2137
|
+
error instanceof StreamResetError
|
|
2138
|
+
) {
|
|
2139
|
+
// A single stream reset need not invalidate a healthy connection.
|
|
2140
|
+
// Retry within the existing attempt/signal budget; a muxer-wide
|
|
2141
|
+
// failure then surfaces on the next open instead of staying cached.
|
|
2142
|
+
continue;
|
|
2047
2143
|
}
|
|
2048
2144
|
|
|
2049
2145
|
throw error;
|
|
@@ -2060,6 +2156,7 @@ export abstract class DirectStream<
|
|
|
2060
2156
|
*/
|
|
2061
2157
|
public async onPeerConnected(peerId: PeerId, connection: Connection) {
|
|
2062
2158
|
if (
|
|
2159
|
+
this.stopping ||
|
|
2063
2160
|
!this.isStarted() ||
|
|
2064
2161
|
connection.limits ||
|
|
2065
2162
|
connection.status !== "open"
|
|
@@ -2069,7 +2166,7 @@ export abstract class DirectStream<
|
|
|
2069
2166
|
const peerKey = getPublicKeyFromPeerId(peerId);
|
|
2070
2167
|
|
|
2071
2168
|
if (this.prunedConnectionsCache?.has(peerKey.hashcode())) {
|
|
2072
|
-
|
|
2169
|
+
connection.abort(new AbortError("Connection was pruned"));
|
|
2073
2170
|
await this.components.peerStore.delete(peerId);
|
|
2074
2171
|
return; // we recently pruned this connect, dont allow it to connect for a while
|
|
2075
2172
|
}
|
|
@@ -2088,6 +2185,8 @@ export abstract class DirectStream<
|
|
|
2088
2185
|
// PeerId could be me, if so, it means that I am disconnecting
|
|
2089
2186
|
const peerKey = getPublicKeyFromPeerId(peerId);
|
|
2090
2187
|
const peerKeyHash = peerKey.hashcode();
|
|
2188
|
+
const currentPeer = this.peers.get(peerKeyHash);
|
|
2189
|
+
if (!currentPeer || (conn && conn.id !== currentPeer.connId)) return;
|
|
2091
2190
|
const allConnections =
|
|
2092
2191
|
this.components.connectionManager.getConnections?.() ?? [];
|
|
2093
2192
|
const connections = allConnections.filter(
|
|
@@ -2110,8 +2209,13 @@ export abstract class DirectStream<
|
|
|
2110
2209
|
return;
|
|
2111
2210
|
}
|
|
2112
2211
|
if (!this.publicKey.equals(peerKey)) {
|
|
2113
|
-
await this._removePeer(peerKey);
|
|
2114
|
-
if (
|
|
2212
|
+
const removed = await this._removePeer(peerKey);
|
|
2213
|
+
if (
|
|
2214
|
+
removed !== currentPeer ||
|
|
2215
|
+
this.peers.has(peerKeyHash) ||
|
|
2216
|
+
this.stopping ||
|
|
2217
|
+
!this.started
|
|
2218
|
+
) {
|
|
2115
2219
|
return;
|
|
2116
2220
|
}
|
|
2117
2221
|
|
|
@@ -2129,7 +2233,7 @@ export abstract class DirectStream<
|
|
|
2129
2233
|
mode: new SilentDelivery({ to: dependent, redundancy: 2 }),
|
|
2130
2234
|
}),
|
|
2131
2235
|
}).sign(this.sign);
|
|
2132
|
-
if (this.stopping || !this.started) {
|
|
2236
|
+
if (this.stopping || !this.started || this.peers.has(peerKeyHash)) {
|
|
2133
2237
|
return;
|
|
2134
2238
|
}
|
|
2135
2239
|
await this.publishMessageMaybe(
|
|
@@ -2252,17 +2356,27 @@ export abstract class DirectStream<
|
|
|
2252
2356
|
protocol: string,
|
|
2253
2357
|
connId: string,
|
|
2254
2358
|
): PeerStreams {
|
|
2359
|
+
if (this.stopping) throw new AbortError("Closed");
|
|
2255
2360
|
const publicKeyHash = publicKey.hashcode();
|
|
2256
2361
|
|
|
2257
2362
|
this.clearHealthcheckTimer(publicKeyHash);
|
|
2258
2363
|
|
|
2259
2364
|
const existing = this.peers.get(publicKeyHash);
|
|
2260
2365
|
|
|
2261
|
-
//
|
|
2262
|
-
if (existing != null) {
|
|
2366
|
+
// Reuse only a live object; close has already made attachments impossible.
|
|
2367
|
+
if (existing != null && !existing.isClosed) {
|
|
2263
2368
|
existing.connId = connId;
|
|
2264
2369
|
return existing;
|
|
2265
2370
|
}
|
|
2371
|
+
if (existing) {
|
|
2372
|
+
if (this.retiredPeerStreams.size >= MAX_RETIRED_PEER_STREAMS) {
|
|
2373
|
+
throw new AbortError("Too many pending peer stream closes");
|
|
2374
|
+
}
|
|
2375
|
+
this.retiredPeerStreams.add(existing);
|
|
2376
|
+
const forget = () => this.retiredPeerStreams.delete(existing);
|
|
2377
|
+
// A failed close remains owned so beforeStop cannot report a clean drain.
|
|
2378
|
+
void existing.close().then(forget, () => {});
|
|
2379
|
+
}
|
|
2266
2380
|
|
|
2267
2381
|
// else create a new peer streams
|
|
2268
2382
|
const peerIdStr = peerId.toString();
|
|
@@ -2280,21 +2394,31 @@ export abstract class DirectStream<
|
|
|
2280
2394
|
});
|
|
2281
2395
|
|
|
2282
2396
|
this.peers.set(publicKeyHash, peerStreams);
|
|
2283
|
-
|
|
2397
|
+
// Object replacement is not evidence of a new authenticated peer session.
|
|
2398
|
+
if (!existing) this.updateSession(publicKey, -1);
|
|
2284
2399
|
|
|
2285
2400
|
// Propagate per-peer stream readiness events to the parent emitter
|
|
2286
|
-
const
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
const
|
|
2401
|
+
const isCurrentPeer = () => this.peers.get(publicKeyHash) === peerStreams;
|
|
2402
|
+
const forwardOutbound = () => {
|
|
2403
|
+
if (isCurrentPeer()) this.dispatchEvent(new CustomEvent("stream:outbound"));
|
|
2404
|
+
};
|
|
2405
|
+
const forwardInbound = () => {
|
|
2406
|
+
if (isCurrentPeer()) this.dispatchEvent(new CustomEvent("stream:inbound"));
|
|
2407
|
+
};
|
|
2408
|
+
const forwardQueue = () => {
|
|
2409
|
+
if (isCurrentPeer()) this.notifyTotalOutboundQueueWaiters();
|
|
2410
|
+
};
|
|
2291
2411
|
peerStreams.addEventListener("stream:outbound", forwardOutbound);
|
|
2292
2412
|
peerStreams.addEventListener("stream:inbound", forwardInbound);
|
|
2293
2413
|
peerStreams.addEventListener("queue:outbound", forwardQueue);
|
|
2294
2414
|
|
|
2295
|
-
peerStreams.addEventListener(
|
|
2296
|
-
|
|
2297
|
-
|
|
2415
|
+
peerStreams.addEventListener(
|
|
2416
|
+
"close",
|
|
2417
|
+
() => {
|
|
2418
|
+
if (isCurrentPeer()) void this._removePeer(publicKey).catch(logError);
|
|
2419
|
+
},
|
|
2420
|
+
{ once: true },
|
|
2421
|
+
);
|
|
2298
2422
|
peerStreams.addEventListener(
|
|
2299
2423
|
"close",
|
|
2300
2424
|
() => {
|
|
@@ -2312,7 +2436,7 @@ export abstract class DirectStream<
|
|
|
2312
2436
|
publicKey,
|
|
2313
2437
|
-1,
|
|
2314
2438
|
+new Date(),
|
|
2315
|
-
-1,
|
|
2439
|
+
existing ? this.routes.getSession(publicKeyHash) ?? -1 : -1,
|
|
2316
2440
|
);
|
|
2317
2441
|
|
|
2318
2442
|
// Enforce connection manager limits eagerly when new peers are added. Without this,
|
|
@@ -2327,7 +2451,9 @@ export abstract class DirectStream<
|
|
|
2327
2451
|
/**
|
|
2328
2452
|
* Notifies the router that a peer has been disconnected
|
|
2329
2453
|
*/
|
|
2330
|
-
protected async _removePeer(
|
|
2454
|
+
protected async _removePeer(
|
|
2455
|
+
publicKey: PublicSignKey,
|
|
2456
|
+
): Promise<PeerStreams | undefined> {
|
|
2331
2457
|
const hash = publicKey.hashcode();
|
|
2332
2458
|
const peerStreams = this.peers.get(hash);
|
|
2333
2459
|
this.clearHealthcheckTimer(hash);
|
|
@@ -2338,6 +2464,7 @@ export abstract class DirectStream<
|
|
|
2338
2464
|
|
|
2339
2465
|
// close peer streams
|
|
2340
2466
|
await peerStreams.close();
|
|
2467
|
+
if (this.peers.get(hash) !== peerStreams) return;
|
|
2341
2468
|
|
|
2342
2469
|
// delete peer streams
|
|
2343
2470
|
logger.trace("delete peer" + publicKey.toString());
|
|
@@ -2358,6 +2485,7 @@ export abstract class DirectStream<
|
|
|
2358
2485
|
let failed = false;
|
|
2359
2486
|
try {
|
|
2360
2487
|
for await (const data of record.iterable) {
|
|
2488
|
+
if (this.peers.get(peerId.hashcode()) !== peerStreams) break;
|
|
2361
2489
|
const now = Date.now();
|
|
2362
2490
|
record.lastActivity = now;
|
|
2363
2491
|
record.bytesReceived += data.length || data.byteLength || 0;
|
|
@@ -2382,14 +2510,21 @@ export abstract class DirectStream<
|
|
|
2382
2510
|
err?.message,
|
|
2383
2511
|
);
|
|
2384
2512
|
}
|
|
2385
|
-
this.
|
|
2513
|
+
if (this.peers.get(peerId.hashcode()) === peerStreams) {
|
|
2514
|
+
void this.onPeerDisconnected(peerStreams.peerId).catch(logError);
|
|
2515
|
+
}
|
|
2386
2516
|
} finally {
|
|
2387
2517
|
const removed = peerStreams.detachInboundStream(
|
|
2388
2518
|
record,
|
|
2389
2519
|
new AbortError("Inbound stream reader ended"),
|
|
2390
2520
|
{ closeRaw: failed },
|
|
2391
2521
|
);
|
|
2392
|
-
if (
|
|
2522
|
+
if (
|
|
2523
|
+
removed &&
|
|
2524
|
+
!failed &&
|
|
2525
|
+
!peerStreams.isReadable &&
|
|
2526
|
+
this.peers.get(peerId.hashcode()) === peerStreams
|
|
2527
|
+
) {
|
|
2393
2528
|
void this.onPeerDisconnected(peerStreams.peerId).catch(logError);
|
|
2394
2529
|
}
|
|
2395
2530
|
}
|
|
@@ -2612,7 +2747,7 @@ export abstract class DirectStream<
|
|
|
2612
2747
|
msg: Uint8ArrayList,
|
|
2613
2748
|
decodedMessage?: Message,
|
|
2614
2749
|
) {
|
|
2615
|
-
if (!this.started) {
|
|
2750
|
+
if (this.stopping || !this.started) {
|
|
2616
2751
|
return;
|
|
2617
2752
|
}
|
|
2618
2753
|
|
|
@@ -4034,6 +4169,7 @@ export abstract class DirectStream<
|
|
|
4034
4169
|
}
|
|
4035
4170
|
|
|
4036
4171
|
async pruneConnections(): Promise<void> {
|
|
4172
|
+
if (this.stopping || !this.started) return;
|
|
4037
4173
|
// TODO sort by bandwidth
|
|
4038
4174
|
if (this.peers.size <= this.connectionManagerOptions.minConnections) {
|
|
4039
4175
|
return;
|
|
@@ -4051,6 +4187,7 @@ export abstract class DirectStream<
|
|
|
4051
4187
|
this.prunedConnectionsCache?.add(stream.publicKey.hashcode());
|
|
4052
4188
|
|
|
4053
4189
|
await this.onPeerDisconnected(stream.peerId);
|
|
4190
|
+
if (this.stopping || !this.started) return;
|
|
4054
4191
|
return this.components.connectionManager.closeConnections(stream.peerId);
|
|
4055
4192
|
}
|
|
4056
4193
|
|