@libp2p/utils 5.4.0-a11e135c2 → 5.4.0-bfa7660d5
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/abort-options.d.ts +7 -0
- package/dist/src/abort-options.d.ts.map +1 -0
- package/dist/src/abort-options.js +14 -0
- package/dist/src/abort-options.js.map +1 -0
- package/dist/src/adaptive-timeout.d.ts +35 -0
- package/dist/src/adaptive-timeout.d.ts.map +1 -0
- package/dist/src/adaptive-timeout.js +63 -0
- package/dist/src/adaptive-timeout.js.map +1 -0
- package/dist/src/close.d.ts +21 -0
- package/dist/src/close.d.ts.map +1 -0
- package/dist/src/close.js +49 -0
- package/dist/src/close.js.map +1 -0
- package/dist/src/moving-average.d.ts +18 -0
- package/dist/src/moving-average.d.ts.map +1 -0
- package/dist/src/moving-average.js +43 -0
- package/dist/src/moving-average.js.map +1 -0
- package/package.json +22 -5
- package/src/abort-options.ts +20 -0
- package/src/adaptive-timeout.ts +94 -0
- package/src/close.ts +65 -0
- package/src/moving-average.ts +45 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AbortOptions } from '@libp2p/interface';
|
|
2
|
+
import type { ClearableSignal } from 'any-signal';
|
|
3
|
+
export declare function createTimeoutOptions(timeout: number): AbortOptions;
|
|
4
|
+
export declare function createTimeoutOptions(timeout: number, ...existingSignals: AbortSignal[]): {
|
|
5
|
+
signal: ClearableSignal;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=abort-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort-options.d.ts","sourceRoot":"","sources":["../../src/abort-options.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,wBAAgB,oBAAoB,CAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAAA;AACpE,wBAAgB,oBAAoB,CAAE,OAAO,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,WAAW,EAAE,GAAG;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { setMaxListeners } from '@libp2p/interface';
|
|
2
|
+
import { anySignal } from 'any-signal';
|
|
3
|
+
export function createTimeoutOptions(timeout, ...existingSignals) {
|
|
4
|
+
let signal = AbortSignal.timeout(timeout);
|
|
5
|
+
setMaxListeners(Infinity, signal);
|
|
6
|
+
if (existingSignals.length > 0) {
|
|
7
|
+
signal = anySignal([signal, ...existingSignals]);
|
|
8
|
+
setMaxListeners(Infinity, signal);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
signal
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=abort-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort-options.js","sourceRoot":"","sources":["../../src/abort-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAMtC,MAAM,UAAU,oBAAoB,CAAE,OAAe,EAAE,GAAG,eAA8B;IACtF,IAAI,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACzC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAEjC,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,GAAG,SAAS,CAAC,CAAC,MAAM,EAAE,GAAG,eAAe,CAAC,CAAC,CAAA;QAChD,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACnC,CAAC;IAED,OAAO;QACL,MAAM;KACP,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ClearableSignal } from 'any-signal';
|
|
2
|
+
import type { Metrics } from '@libp2p/interface';
|
|
3
|
+
export declare const DEFAULT_TIMEOUT_MULTIPLIER = 1.2;
|
|
4
|
+
export declare const DEFAULT_FAILURE_MULTIPLIER = 2;
|
|
5
|
+
export declare const DEFAULT_MIN_TIMEOUT = 2000;
|
|
6
|
+
export interface AdaptiveTimeoutSignal extends ClearableSignal {
|
|
7
|
+
start: number;
|
|
8
|
+
timeout: number;
|
|
9
|
+
}
|
|
10
|
+
export interface AdaptiveTimeoutInit {
|
|
11
|
+
metricName?: string;
|
|
12
|
+
metrics?: Metrics;
|
|
13
|
+
interval?: number;
|
|
14
|
+
initialValue?: number;
|
|
15
|
+
timeoutMultiplier?: number;
|
|
16
|
+
failureMultiplier?: number;
|
|
17
|
+
minTimeout?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface GetTimeoutSignalOptions {
|
|
20
|
+
timeoutFactor?: number;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
export declare class AdaptiveTimeout {
|
|
24
|
+
private readonly success;
|
|
25
|
+
private readonly failure;
|
|
26
|
+
private readonly next;
|
|
27
|
+
private readonly metric?;
|
|
28
|
+
private readonly timeoutMultiplier;
|
|
29
|
+
private readonly failureMultiplier;
|
|
30
|
+
private readonly minTimeout;
|
|
31
|
+
constructor(init?: AdaptiveTimeoutInit);
|
|
32
|
+
getTimeoutSignal(options?: GetTimeoutSignalOptions): AdaptiveTimeoutSignal;
|
|
33
|
+
cleanUp(signal: AdaptiveTimeoutSignal): void;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=adaptive-timeout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-timeout.d.ts","sourceRoot":"","sources":["../../src/adaptive-timeout.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5D,OAAO,KAAK,EAAe,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAE7D,eAAO,MAAM,0BAA0B,MAAM,CAAA;AAC7C,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAC3C,eAAO,MAAM,mBAAmB,OAAO,CAAA;AAEvC,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAQ;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAQ;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;gBAEtB,IAAI,GAAE,mBAAwB;IAa3C,gBAAgB,CAAE,OAAO,GAAE,uBAA4B,GAAG,qBAAqB;IAiB/E,OAAO,CAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI;CAyB9C"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { setMaxListeners } from '@libp2p/interface';
|
|
2
|
+
import { anySignal } from 'any-signal';
|
|
3
|
+
import { MovingAverage } from './moving-average.js';
|
|
4
|
+
export const DEFAULT_TIMEOUT_MULTIPLIER = 1.2;
|
|
5
|
+
export const DEFAULT_FAILURE_MULTIPLIER = 2;
|
|
6
|
+
export const DEFAULT_MIN_TIMEOUT = 2000;
|
|
7
|
+
export class AdaptiveTimeout {
|
|
8
|
+
success;
|
|
9
|
+
failure;
|
|
10
|
+
next;
|
|
11
|
+
metric;
|
|
12
|
+
timeoutMultiplier;
|
|
13
|
+
failureMultiplier;
|
|
14
|
+
minTimeout;
|
|
15
|
+
constructor(init = {}) {
|
|
16
|
+
this.success = new MovingAverage(init.interval ?? 5000);
|
|
17
|
+
this.failure = new MovingAverage(init.interval ?? 5000);
|
|
18
|
+
this.next = new MovingAverage(init.interval ?? 5000);
|
|
19
|
+
this.failureMultiplier = init.failureMultiplier ?? DEFAULT_FAILURE_MULTIPLIER;
|
|
20
|
+
this.timeoutMultiplier = init.timeoutMultiplier ?? DEFAULT_TIMEOUT_MULTIPLIER;
|
|
21
|
+
this.minTimeout = init.minTimeout ?? DEFAULT_MIN_TIMEOUT;
|
|
22
|
+
if (init.metricName != null) {
|
|
23
|
+
this.metric = init.metrics?.registerMetricGroup(init.metricName);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
getTimeoutSignal(options = {}) {
|
|
27
|
+
// calculate timeout for individual peers based on moving average of
|
|
28
|
+
// previous successful requests
|
|
29
|
+
const timeout = Math.max(Math.round(this.next.movingAverage * (options.timeoutFactor ?? this.timeoutMultiplier)), this.minTimeout);
|
|
30
|
+
const sendTimeout = AbortSignal.timeout(timeout);
|
|
31
|
+
const timeoutSignal = anySignal([options.signal, sendTimeout]);
|
|
32
|
+
setMaxListeners(Infinity, timeoutSignal, sendTimeout);
|
|
33
|
+
timeoutSignal.start = Date.now();
|
|
34
|
+
timeoutSignal.timeout = timeout;
|
|
35
|
+
return timeoutSignal;
|
|
36
|
+
}
|
|
37
|
+
cleanUp(signal) {
|
|
38
|
+
const time = Date.now() - signal.start;
|
|
39
|
+
if (signal.aborted) {
|
|
40
|
+
this.failure.push(time);
|
|
41
|
+
this.next.push(time * this.failureMultiplier);
|
|
42
|
+
this.metric?.update({
|
|
43
|
+
failureMovingAverage: this.failure.movingAverage,
|
|
44
|
+
failureDeviation: this.failure.deviation,
|
|
45
|
+
failureForecast: this.failure.forecast,
|
|
46
|
+
failureVariance: this.failure.variance,
|
|
47
|
+
failure: time
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.success.push(time);
|
|
52
|
+
this.next.push(time);
|
|
53
|
+
this.metric?.update({
|
|
54
|
+
successMovingAverage: this.success.movingAverage,
|
|
55
|
+
successDeviation: this.success.deviation,
|
|
56
|
+
successForecast: this.success.forecast,
|
|
57
|
+
successVariance: this.success.variance,
|
|
58
|
+
success: time
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=adaptive-timeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-timeout.js","sourceRoot":"","sources":["../../src/adaptive-timeout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Connection, Stream, AbortOptions } from '@libp2p/interface';
|
|
2
|
+
/**
|
|
3
|
+
* Close the passed stream, falling back to aborting the stream if closing
|
|
4
|
+
* cleanly fails.
|
|
5
|
+
*/
|
|
6
|
+
export declare function safelyCloseStream(stream?: Stream, options?: AbortOptions): Promise<void>;
|
|
7
|
+
export interface SafelyCloseConnectionOptions extends AbortOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Only close the stream if it either has no protocol streams open or only
|
|
10
|
+
* ones in this list.
|
|
11
|
+
*
|
|
12
|
+
* @default ['/ipfs/id/1.0.0']
|
|
13
|
+
*/
|
|
14
|
+
closableProtocols?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Close the passed connection if it has no streams, or only closable protocol
|
|
18
|
+
* streams, falling back to aborting the connection if closing it cleanly fails.
|
|
19
|
+
*/
|
|
20
|
+
export declare function safelyCloseConnectionIfUnused(connection?: Connection, options?: SafelyCloseConnectionOptions): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=close.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/close.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEzE;;;GAGG;AACH,wBAAsB,iBAAiB,CAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/F;AAuBD,MAAM,WAAW,4BAA6B,SAAQ,YAAY;IAChE;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC7B;AAED;;;GAGG;AACH,wBAAsB,6BAA6B,CAAE,UAAU,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,4BAA4B,GAAG,OAAO,CAAC,IAAI,CAAC,CAenI"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Close the passed stream, falling back to aborting the stream if closing
|
|
3
|
+
* cleanly fails.
|
|
4
|
+
*/
|
|
5
|
+
export async function safelyCloseStream(stream, options) {
|
|
6
|
+
try {
|
|
7
|
+
await stream?.close(options);
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
stream?.abort(err);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* These are speculative protocols that are run automatically on connection open
|
|
15
|
+
* so are usually not the reason the connection was opened.
|
|
16
|
+
*
|
|
17
|
+
* Consequently when requested it should be safe to close connections that only
|
|
18
|
+
* have these protocol streams open.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_CLOSABLE_PROTOCOLS = [
|
|
21
|
+
// identify
|
|
22
|
+
'/ipfs/id/1.0.0',
|
|
23
|
+
// identify-push
|
|
24
|
+
'/ipfs/id/push/1.0.0',
|
|
25
|
+
// autonat
|
|
26
|
+
'/libp2p/autonat/1.0.0',
|
|
27
|
+
// dcutr
|
|
28
|
+
'/libp2p/dcutr'
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Close the passed connection if it has no streams, or only closable protocol
|
|
32
|
+
* streams, falling back to aborting the connection if closing it cleanly fails.
|
|
33
|
+
*/
|
|
34
|
+
export async function safelyCloseConnectionIfUnused(connection, options) {
|
|
35
|
+
const streamProtocols = connection?.streams?.map(stream => stream.protocol) ?? [];
|
|
36
|
+
const closableProtocols = options?.closableProtocols ?? DEFAULT_CLOSABLE_PROTOCOLS;
|
|
37
|
+
// if the connection has protocols not in the closable protocols list, do not
|
|
38
|
+
// close the connection
|
|
39
|
+
if (streamProtocols.filter(proto => proto != null && !closableProtocols.includes(proto)).length > 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
await connection?.close(options);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
connection?.abort(err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=close.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close.js","sourceRoot":"","sources":["../../src/close.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAE,MAAe,EAAE,OAAsB;IAC9E,IAAI,CAAC;QACH,MAAM,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IACpB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,0BAA0B,GAAG;IACjC,WAAW;IACX,gBAAgB;IAEhB,gBAAgB;IAChB,qBAAqB;IAErB,UAAU;IACV,uBAAuB;IAEvB,QAAQ;IACR,eAAe;CAChB,CAAA;AAYD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CAAE,UAAuB,EAAE,OAAsC;IAClH,MAAM,eAAe,GAAG,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IACjF,MAAM,iBAAiB,GAAG,OAAO,EAAE,iBAAiB,IAAI,0BAA0B,CAAA;IAElF,6EAA6E;IAC7E,uBAAuB;IACvB,IAAI,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpG,OAAM;IACR,CAAC;IAED,IAAI,CAAC;QACH,MAAM,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements exponential moving average. Ported from `moving-average`.
|
|
3
|
+
*
|
|
4
|
+
* @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
|
|
5
|
+
* @see https://www.npmjs.com/package/moving-average
|
|
6
|
+
*/
|
|
7
|
+
export declare class MovingAverage {
|
|
8
|
+
movingAverage: number;
|
|
9
|
+
variance: number;
|
|
10
|
+
deviation: number;
|
|
11
|
+
forecast: number;
|
|
12
|
+
private readonly timespan;
|
|
13
|
+
private previousTime?;
|
|
14
|
+
constructor(timespan: number);
|
|
15
|
+
alpha(t: number, pt: number): number;
|
|
16
|
+
push(value: number, time?: number): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=moving-average.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moving-average.d.ts","sourceRoot":"","sources":["../../src/moving-average.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,aAAa;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,YAAY,CAAC,CAAQ;gBAEhB,QAAQ,EAAE,MAAM;IAQ7B,KAAK,CAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM;IAIrC,IAAI,CAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAmB,GAAG,IAAI;CAkBtD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements exponential moving average. Ported from `moving-average`.
|
|
3
|
+
*
|
|
4
|
+
* @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
|
|
5
|
+
* @see https://www.npmjs.com/package/moving-average
|
|
6
|
+
*/
|
|
7
|
+
export class MovingAverage {
|
|
8
|
+
movingAverage;
|
|
9
|
+
variance;
|
|
10
|
+
deviation;
|
|
11
|
+
forecast;
|
|
12
|
+
timespan;
|
|
13
|
+
previousTime;
|
|
14
|
+
constructor(timespan) {
|
|
15
|
+
this.timespan = timespan;
|
|
16
|
+
this.movingAverage = 0;
|
|
17
|
+
this.variance = 0;
|
|
18
|
+
this.deviation = 0;
|
|
19
|
+
this.forecast = 0;
|
|
20
|
+
}
|
|
21
|
+
alpha(t, pt) {
|
|
22
|
+
return 1 - (Math.exp(-(t - pt) / this.timespan));
|
|
23
|
+
}
|
|
24
|
+
push(value, time = Date.now()) {
|
|
25
|
+
if (this.previousTime != null) {
|
|
26
|
+
// calculate moving average
|
|
27
|
+
const a = this.alpha(time, this.previousTime);
|
|
28
|
+
const diff = value - this.movingAverage;
|
|
29
|
+
const incr = a * diff;
|
|
30
|
+
this.movingAverage = a * value + (1 - a) * this.movingAverage;
|
|
31
|
+
// calculate variance & deviation
|
|
32
|
+
this.variance = (1 - a) * (this.variance + diff * incr);
|
|
33
|
+
this.deviation = Math.sqrt(this.variance);
|
|
34
|
+
// calculate forecast
|
|
35
|
+
this.forecast = this.movingAverage + a * diff;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.movingAverage = value;
|
|
39
|
+
}
|
|
40
|
+
this.previousTime = time;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=moving-average.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moving-average.js","sourceRoot":"","sources":["../../src/moving-average.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IACjB,aAAa,CAAQ;IACrB,QAAQ,CAAQ;IAChB,SAAS,CAAQ;IACjB,QAAQ,CAAQ;IACN,QAAQ,CAAQ;IACzB,YAAY,CAAS;IAE7B,YAAa,QAAgB;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;QACjB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QAClB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,KAAK,CAAE,CAAS,EAAE,EAAU;QAC1B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;IAClD,CAAC;IAED,IAAI,CAAE,KAAa,EAAE,OAAe,IAAI,CAAC,GAAG,EAAE;QAC5C,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;YAC9B,2BAA2B;YAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAA;YAC7C,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,aAAa,CAAA;YACvC,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAA;YACrB,IAAI,CAAC,aAAa,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,aAAa,CAAA;YAC7D,iCAAiC;YACjC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,CAAA;YACvD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACzC,qBAAqB;YACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,aAAa,GAAG,CAAC,GAAG,IAAI,CAAA;QAC/C,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,aAAa,GAAG,KAAK,CAAA;QAC5B,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;IAC1B,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libp2p/utils",
|
|
3
|
-
"version": "5.4.0-
|
|
3
|
+
"version": "5.4.0-bfa7660d5",
|
|
4
4
|
"description": "Package to aggregate shared logic and dependencies for the libp2p ecosystem",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/utils#readme",
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"types": "./src/index.d.ts",
|
|
45
45
|
"import": "./dist/src/index.js"
|
|
46
46
|
},
|
|
47
|
+
"./abort-options": {
|
|
48
|
+
"types": "./dist/src/abort-options.d.ts",
|
|
49
|
+
"import": "./dist/src/abort-options.js"
|
|
50
|
+
},
|
|
47
51
|
"./abstract-stream": {
|
|
48
52
|
"types": "./dist/src/abstract-stream.d.ts",
|
|
49
53
|
"import": "./dist/src/abstract-stream.js"
|
|
@@ -52,6 +56,10 @@
|
|
|
52
56
|
"types": "./dist/src/address-sort.d.ts",
|
|
53
57
|
"import": "./dist/src/address-sort.js"
|
|
54
58
|
},
|
|
59
|
+
"./adaptive-timeout": {
|
|
60
|
+
"types": "./dist/src/adaptive-timeout.d.ts",
|
|
61
|
+
"import": "./dist/src/adaptive-timeout.js"
|
|
62
|
+
},
|
|
55
63
|
"./array-equals": {
|
|
56
64
|
"types": "./dist/src/array-equals.d.ts",
|
|
57
65
|
"import": "./dist/src/array-equals.js"
|
|
@@ -60,6 +68,10 @@
|
|
|
60
68
|
"types": "./dist/src/close-source.d.ts",
|
|
61
69
|
"import": "./dist/src/close-source.js"
|
|
62
70
|
},
|
|
71
|
+
"./close": {
|
|
72
|
+
"types": "./dist/src/close.d.ts",
|
|
73
|
+
"import": "./dist/src/close.js"
|
|
74
|
+
},
|
|
63
75
|
"./filters": {
|
|
64
76
|
"types": "./dist/src/filters/index.d.ts",
|
|
65
77
|
"import": "./dist/src/filters/index.js"
|
|
@@ -72,6 +84,10 @@
|
|
|
72
84
|
"types": "./dist/src/is-promise.d.ts",
|
|
73
85
|
"import": "./dist/src/is-promise.js"
|
|
74
86
|
},
|
|
87
|
+
"./moving-average": {
|
|
88
|
+
"types": "./dist/src/moving-average.d.ts",
|
|
89
|
+
"import": "./dist/src/moving-average.js"
|
|
90
|
+
},
|
|
75
91
|
"./multiaddr/is-loopback": {
|
|
76
92
|
"types": "./dist/src/multiaddr/is-loopback.d.ts",
|
|
77
93
|
"import": "./dist/src/multiaddr/is-loopback.js"
|
|
@@ -132,13 +148,14 @@
|
|
|
132
148
|
},
|
|
133
149
|
"dependencies": {
|
|
134
150
|
"@chainsafe/is-ip": "^2.0.2",
|
|
135
|
-
"@libp2p/crypto": "4.1.1-
|
|
136
|
-
"@libp2p/interface": "1.3.1-
|
|
137
|
-
"@libp2p/logger": "4.0.12-
|
|
151
|
+
"@libp2p/crypto": "4.1.1-bfa7660d5",
|
|
152
|
+
"@libp2p/interface": "1.3.1-bfa7660d5",
|
|
153
|
+
"@libp2p/logger": "4.0.12-bfa7660d5",
|
|
138
154
|
"@multiformats/multiaddr": "^12.2.1",
|
|
139
155
|
"@multiformats/multiaddr-matcher": "^1.2.0",
|
|
140
156
|
"@sindresorhus/fnv1a": "^3.1.0",
|
|
141
157
|
"@types/murmurhash3js-revisited": "^3.0.3",
|
|
158
|
+
"any-signal": "^4.1.1",
|
|
142
159
|
"delay": "^6.0.0",
|
|
143
160
|
"get-iterator": "^2.0.1",
|
|
144
161
|
"is-loopback-addr": "^2.0.2",
|
|
@@ -153,7 +170,7 @@
|
|
|
153
170
|
"uint8arrays": "^5.0.3"
|
|
154
171
|
},
|
|
155
172
|
"devDependencies": {
|
|
156
|
-
"@libp2p/peer-id-factory": "4.1.1-
|
|
173
|
+
"@libp2p/peer-id-factory": "4.1.1-bfa7660d5",
|
|
157
174
|
"@types/netmask": "^2.0.5",
|
|
158
175
|
"aegir": "^42.2.5",
|
|
159
176
|
"delay": "^6.0.0",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { setMaxListeners } from '@libp2p/interface'
|
|
2
|
+
import { anySignal } from 'any-signal'
|
|
3
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
4
|
+
import type { ClearableSignal } from 'any-signal'
|
|
5
|
+
|
|
6
|
+
export function createTimeoutOptions (timeout: number): AbortOptions
|
|
7
|
+
export function createTimeoutOptions (timeout: number, ...existingSignals: AbortSignal[]): { signal: ClearableSignal }
|
|
8
|
+
export function createTimeoutOptions (timeout: number, ...existingSignals: AbortSignal[]): AbortOptions {
|
|
9
|
+
let signal = AbortSignal.timeout(timeout)
|
|
10
|
+
setMaxListeners(Infinity, signal)
|
|
11
|
+
|
|
12
|
+
if (existingSignals.length > 0) {
|
|
13
|
+
signal = anySignal([signal, ...existingSignals])
|
|
14
|
+
setMaxListeners(Infinity, signal)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
signal
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { setMaxListeners } from '@libp2p/interface'
|
|
2
|
+
import { anySignal, type ClearableSignal } from 'any-signal'
|
|
3
|
+
import { MovingAverage } from './moving-average.js'
|
|
4
|
+
import type { MetricGroup, Metrics } from '@libp2p/interface'
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_TIMEOUT_MULTIPLIER = 1.2
|
|
7
|
+
export const DEFAULT_FAILURE_MULTIPLIER = 2
|
|
8
|
+
export const DEFAULT_MIN_TIMEOUT = 2000
|
|
9
|
+
|
|
10
|
+
export interface AdaptiveTimeoutSignal extends ClearableSignal {
|
|
11
|
+
start: number
|
|
12
|
+
timeout: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AdaptiveTimeoutInit {
|
|
16
|
+
metricName?: string
|
|
17
|
+
metrics?: Metrics
|
|
18
|
+
interval?: number
|
|
19
|
+
initialValue?: number
|
|
20
|
+
timeoutMultiplier?: number
|
|
21
|
+
failureMultiplier?: number
|
|
22
|
+
minTimeout?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GetTimeoutSignalOptions {
|
|
26
|
+
timeoutFactor?: number
|
|
27
|
+
signal?: AbortSignal
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class AdaptiveTimeout {
|
|
31
|
+
private readonly success: MovingAverage
|
|
32
|
+
private readonly failure: MovingAverage
|
|
33
|
+
private readonly next: MovingAverage
|
|
34
|
+
private readonly metric?: MetricGroup
|
|
35
|
+
private readonly timeoutMultiplier: number
|
|
36
|
+
private readonly failureMultiplier: number
|
|
37
|
+
private readonly minTimeout: number
|
|
38
|
+
|
|
39
|
+
constructor (init: AdaptiveTimeoutInit = {}) {
|
|
40
|
+
this.success = new MovingAverage(init.interval ?? 5000)
|
|
41
|
+
this.failure = new MovingAverage(init.interval ?? 5000)
|
|
42
|
+
this.next = new MovingAverage(init.interval ?? 5000)
|
|
43
|
+
this.failureMultiplier = init.failureMultiplier ?? DEFAULT_FAILURE_MULTIPLIER
|
|
44
|
+
this.timeoutMultiplier = init.timeoutMultiplier ?? DEFAULT_TIMEOUT_MULTIPLIER
|
|
45
|
+
this.minTimeout = init.minTimeout ?? DEFAULT_MIN_TIMEOUT
|
|
46
|
+
|
|
47
|
+
if (init.metricName != null) {
|
|
48
|
+
this.metric = init.metrics?.registerMetricGroup(init.metricName)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getTimeoutSignal (options: GetTimeoutSignalOptions = {}): AdaptiveTimeoutSignal {
|
|
53
|
+
// calculate timeout for individual peers based on moving average of
|
|
54
|
+
// previous successful requests
|
|
55
|
+
const timeout = Math.max(
|
|
56
|
+
Math.round(this.next.movingAverage * (options.timeoutFactor ?? this.timeoutMultiplier)),
|
|
57
|
+
this.minTimeout
|
|
58
|
+
)
|
|
59
|
+
const sendTimeout = AbortSignal.timeout(timeout)
|
|
60
|
+
const timeoutSignal = anySignal([options.signal, sendTimeout]) as AdaptiveTimeoutSignal
|
|
61
|
+
setMaxListeners(Infinity, timeoutSignal, sendTimeout)
|
|
62
|
+
|
|
63
|
+
timeoutSignal.start = Date.now()
|
|
64
|
+
timeoutSignal.timeout = timeout
|
|
65
|
+
|
|
66
|
+
return timeoutSignal
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cleanUp (signal: AdaptiveTimeoutSignal): void {
|
|
70
|
+
const time = Date.now() - signal.start
|
|
71
|
+
|
|
72
|
+
if (signal.aborted) {
|
|
73
|
+
this.failure.push(time)
|
|
74
|
+
this.next.push(time * this.failureMultiplier)
|
|
75
|
+
this.metric?.update({
|
|
76
|
+
failureMovingAverage: this.failure.movingAverage,
|
|
77
|
+
failureDeviation: this.failure.deviation,
|
|
78
|
+
failureForecast: this.failure.forecast,
|
|
79
|
+
failureVariance: this.failure.variance,
|
|
80
|
+
failure: time
|
|
81
|
+
})
|
|
82
|
+
} else {
|
|
83
|
+
this.success.push(time)
|
|
84
|
+
this.next.push(time)
|
|
85
|
+
this.metric?.update({
|
|
86
|
+
successMovingAverage: this.success.movingAverage,
|
|
87
|
+
successDeviation: this.success.deviation,
|
|
88
|
+
successForecast: this.success.forecast,
|
|
89
|
+
successVariance: this.success.variance,
|
|
90
|
+
success: time
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/close.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Connection, Stream, AbortOptions } from '@libp2p/interface'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Close the passed stream, falling back to aborting the stream if closing
|
|
5
|
+
* cleanly fails.
|
|
6
|
+
*/
|
|
7
|
+
export async function safelyCloseStream (stream?: Stream, options?: AbortOptions): Promise<void> {
|
|
8
|
+
try {
|
|
9
|
+
await stream?.close(options)
|
|
10
|
+
} catch (err: any) {
|
|
11
|
+
stream?.abort(err)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* These are speculative protocols that are run automatically on connection open
|
|
17
|
+
* so are usually not the reason the connection was opened.
|
|
18
|
+
*
|
|
19
|
+
* Consequently when requested it should be safe to close connections that only
|
|
20
|
+
* have these protocol streams open.
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_CLOSABLE_PROTOCOLS = [
|
|
23
|
+
// identify
|
|
24
|
+
'/ipfs/id/1.0.0',
|
|
25
|
+
|
|
26
|
+
// identify-push
|
|
27
|
+
'/ipfs/id/push/1.0.0',
|
|
28
|
+
|
|
29
|
+
// autonat
|
|
30
|
+
'/libp2p/autonat/1.0.0',
|
|
31
|
+
|
|
32
|
+
// dcutr
|
|
33
|
+
'/libp2p/dcutr'
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
export interface SafelyCloseConnectionOptions extends AbortOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Only close the stream if it either has no protocol streams open or only
|
|
39
|
+
* ones in this list.
|
|
40
|
+
*
|
|
41
|
+
* @default ['/ipfs/id/1.0.0']
|
|
42
|
+
*/
|
|
43
|
+
closableProtocols?: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Close the passed connection if it has no streams, or only closable protocol
|
|
48
|
+
* streams, falling back to aborting the connection if closing it cleanly fails.
|
|
49
|
+
*/
|
|
50
|
+
export async function safelyCloseConnectionIfUnused (connection?: Connection, options?: SafelyCloseConnectionOptions): Promise<void> {
|
|
51
|
+
const streamProtocols = connection?.streams?.map(stream => stream.protocol) ?? []
|
|
52
|
+
const closableProtocols = options?.closableProtocols ?? DEFAULT_CLOSABLE_PROTOCOLS
|
|
53
|
+
|
|
54
|
+
// if the connection has protocols not in the closable protocols list, do not
|
|
55
|
+
// close the connection
|
|
56
|
+
if (streamProtocols.filter(proto => proto != null && !closableProtocols.includes(proto)).length > 0) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await connection?.close(options)
|
|
62
|
+
} catch (err: any) {
|
|
63
|
+
connection?.abort(err)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements exponential moving average. Ported from `moving-average`.
|
|
3
|
+
*
|
|
4
|
+
* @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
|
|
5
|
+
* @see https://www.npmjs.com/package/moving-average
|
|
6
|
+
*/
|
|
7
|
+
export class MovingAverage {
|
|
8
|
+
public movingAverage: number
|
|
9
|
+
public variance: number
|
|
10
|
+
public deviation: number
|
|
11
|
+
public forecast: number
|
|
12
|
+
private readonly timespan: number
|
|
13
|
+
private previousTime?: number
|
|
14
|
+
|
|
15
|
+
constructor (timespan: number) {
|
|
16
|
+
this.timespan = timespan
|
|
17
|
+
this.movingAverage = 0
|
|
18
|
+
this.variance = 0
|
|
19
|
+
this.deviation = 0
|
|
20
|
+
this.forecast = 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
alpha (t: number, pt: number): number {
|
|
24
|
+
return 1 - (Math.exp(-(t - pt) / this.timespan))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
push (value: number, time: number = Date.now()): void {
|
|
28
|
+
if (this.previousTime != null) {
|
|
29
|
+
// calculate moving average
|
|
30
|
+
const a = this.alpha(time, this.previousTime)
|
|
31
|
+
const diff = value - this.movingAverage
|
|
32
|
+
const incr = a * diff
|
|
33
|
+
this.movingAverage = a * value + (1 - a) * this.movingAverage
|
|
34
|
+
// calculate variance & deviation
|
|
35
|
+
this.variance = (1 - a) * (this.variance + diff * incr)
|
|
36
|
+
this.deviation = Math.sqrt(this.variance)
|
|
37
|
+
// calculate forecast
|
|
38
|
+
this.forecast = this.movingAverage + a * diff
|
|
39
|
+
} else {
|
|
40
|
+
this.movingAverage = value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.previousTime = time
|
|
44
|
+
}
|
|
45
|
+
}
|