socket-function 1.1.18 → 1.1.20
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/SocketFunction.ts +1 -1
- package/index.d.ts +4 -11
- package/package.json +1 -1
- package/src/CallFactory.d.ts +0 -2
- package/src/CallFactory.ts +1 -28
- package/src/promiseRace.d.ts +4 -9
- package/src/promiseRace.ts +81 -155
- package/src/webSocketServer.ts +1 -2
package/SocketFunction.ts
CHANGED
|
@@ -16,7 +16,7 @@ import "./SetProcessVariables";
|
|
|
16
16
|
import cborx from "cbor-x";
|
|
17
17
|
import { setFlag } from "./require/compileFlags";
|
|
18
18
|
import { isNode } from "./src/misc";
|
|
19
|
-
import { getPendingCallCount, harvestCallTimes, harvestFailedCallCount
|
|
19
|
+
import { getPendingCallCount, harvestCallTimes, harvestFailedCallCount } from "./src/CallFactory";
|
|
20
20
|
import { measureWrap } from "./src/profiling/measure";
|
|
21
21
|
|
|
22
22
|
setFlag(require, "cbor-x", "allowclient", true);
|
package/index.d.ts
CHANGED
|
@@ -495,7 +495,6 @@ declare module "socket-function/src/CallFactory" {
|
|
|
495
495
|
}
|
|
496
496
|
type InitializeState = {
|
|
497
497
|
supportsLZ4?: boolean;
|
|
498
|
-
ownReconnectionUrl?: string;
|
|
499
498
|
};
|
|
500
499
|
export declare function harvestFailedCallCount(): number;
|
|
501
500
|
export declare function getPendingCallCount(): number;
|
|
@@ -504,7 +503,6 @@ declare module "socket-function/src/CallFactory" {
|
|
|
504
503
|
end: number;
|
|
505
504
|
}[];
|
|
506
505
|
export declare function createCallFactory(webSocketBase: SenderInterface | undefined, nodeId: string, localNodeId?: string): Promise<CallFactory>;
|
|
507
|
-
export declare function registerOwnReconnectionUrl(url: string): void;
|
|
508
506
|
export {};
|
|
509
507
|
|
|
510
508
|
}
|
|
@@ -1267,16 +1265,11 @@ declare module "socket-function/src/profiling/tcpLagProxy" {
|
|
|
1267
1265
|
}
|
|
1268
1266
|
|
|
1269
1267
|
declare module "socket-function/src/promiseRace" {
|
|
1270
|
-
|
|
1271
|
-
constructor(executor: ((resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) | undefined);
|
|
1272
|
-
}
|
|
1273
|
-
/** A promise race function which doesn't leak, unlike Promise.race
|
|
1274
|
-
|
|
1275
|
-
See https://github.com/nodejs/node/issues/17469
|
|
1276
|
-
See https://bugs.chromium.org/p/v8/issues/detail?id=9858#c9
|
|
1277
|
-
|
|
1268
|
+
/** Fixed Promise.race, which doesn't leak promises values. Promises still leak the Promise object themselves, but a Promise is < 100 bytes, where as the promise VALUE might be arbitrarily large.
|
|
1278
1269
|
*/
|
|
1279
|
-
export declare function
|
|
1270
|
+
export declare function PromiseRace<T extends any[]>(promises: {
|
|
1271
|
+
[K in keyof T]: Promise<T[K]>;
|
|
1272
|
+
}): Promise<T[number]>;
|
|
1280
1273
|
|
|
1281
1274
|
}
|
|
1282
1275
|
|
package/package.json
CHANGED
package/src/CallFactory.d.ts
CHANGED
|
@@ -34,7 +34,6 @@ export interface SenderInterface {
|
|
|
34
34
|
}
|
|
35
35
|
type InitializeState = {
|
|
36
36
|
supportsLZ4?: boolean;
|
|
37
|
-
ownReconnectionUrl?: string;
|
|
38
37
|
};
|
|
39
38
|
export declare function harvestFailedCallCount(): number;
|
|
40
39
|
export declare function getPendingCallCount(): number;
|
|
@@ -43,5 +42,4 @@ export declare function harvestCallTimes(): {
|
|
|
43
42
|
end: number;
|
|
44
43
|
}[];
|
|
45
44
|
export declare function createCallFactory(webSocketBase: SenderInterface | undefined, nodeId: string, localNodeId?: string): Promise<CallFactory>;
|
|
46
|
-
export declare function registerOwnReconnectionUrl(url: string): void;
|
|
47
45
|
export {};
|
package/src/CallFactory.ts
CHANGED
|
@@ -78,7 +78,6 @@ export interface SenderInterface {
|
|
|
78
78
|
|
|
79
79
|
type InitializeState = {
|
|
80
80
|
supportsLZ4?: boolean;
|
|
81
|
-
ownReconnectionUrl?: string;
|
|
82
81
|
};
|
|
83
82
|
|
|
84
83
|
const INITIALIZE_STATE_SEQ_NUM = -1;
|
|
@@ -112,8 +111,7 @@ export async function createCallFactory(
|
|
|
112
111
|
// The node id we are connecting to (or that connected to us)
|
|
113
112
|
nodeId: string,
|
|
114
113
|
// The node id that we were contacted on
|
|
115
|
-
|
|
116
|
-
localNodeId = ownReconnectionUrl || "",
|
|
114
|
+
localNodeId = "",
|
|
117
115
|
): Promise<CallFactory> {
|
|
118
116
|
let niceConnectionName = nodeId;
|
|
119
117
|
|
|
@@ -560,28 +558,9 @@ export async function createCallFactory(
|
|
|
560
558
|
if (call.isReturn) {
|
|
561
559
|
if (!SocketFunction.LEGACY_INITIALIZE && call.seqNum === INITIALIZE_STATE_SEQ_NUM) {
|
|
562
560
|
callFactory.receivedInitializeState = call.result as InitializeState;
|
|
563
|
-
let otherReconnectionUrl = callFactory.receivedInitializeState.ownReconnectionUrl;
|
|
564
|
-
callFactory.realNodeId = otherReconnectionUrl;
|
|
565
561
|
if (SocketFunction.logMessages) {
|
|
566
562
|
console.log(green(`Received initialize state from ${callFactory.realNodeId} (for ${nodeId}) at ${Date.now()}`));
|
|
567
563
|
}
|
|
568
|
-
if (otherReconnectionUrl && !canReconnect && !!getNodeIdLocation(otherReconnectionUrl) && otherReconnectionUrl !== ownReconnectionUrl) {
|
|
569
|
-
if (changeNodeId({
|
|
570
|
-
originalNodeId: nodeId,
|
|
571
|
-
newNodeId: otherReconnectionUrl,
|
|
572
|
-
callFactory,
|
|
573
|
-
})) {
|
|
574
|
-
if (SocketFunction.logMessages) {
|
|
575
|
-
console.log(green(`Changed node id from ${nodeId} to ${otherReconnectionUrl}`));
|
|
576
|
-
}
|
|
577
|
-
nodeId = otherReconnectionUrl;
|
|
578
|
-
niceConnectionName = otherReconnectionUrl;
|
|
579
|
-
callerContext.nodeId = nodeId;
|
|
580
|
-
canReconnect = !!getNodeIdLocation(nodeId);
|
|
581
|
-
callFactory.nodeId = nodeId;
|
|
582
|
-
callFactory.connectionId = { nodeId };
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
564
|
return;
|
|
586
565
|
}
|
|
587
566
|
let callbackObj = pendingCalls.get(call.seqNum);
|
|
@@ -794,7 +773,6 @@ export async function createCallFactory(
|
|
|
794
773
|
if (!SocketFunction.LEGACY_INITIALIZE) {
|
|
795
774
|
let initState: InitializeState = {
|
|
796
775
|
supportsLZ4: true,
|
|
797
|
-
ownReconnectionUrl,
|
|
798
776
|
};
|
|
799
777
|
let initReturn: InternalReturnType = {
|
|
800
778
|
isReturn: true,
|
|
@@ -814,11 +792,6 @@ export async function createCallFactory(
|
|
|
814
792
|
return callFactory;
|
|
815
793
|
}
|
|
816
794
|
|
|
817
|
-
let ownReconnectionUrl: string | undefined;
|
|
818
|
-
export function registerOwnReconnectionUrl(url: string) {
|
|
819
|
-
ownReconnectionUrl = url;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
795
|
|
|
823
796
|
let uncompressedSent = 0;
|
|
824
797
|
let compressedSent = 0;
|
package/src/promiseRace.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
constructor(executor: ((resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) | undefined);
|
|
3
|
-
}
|
|
4
|
-
/** A promise race function which doesn't leak, unlike Promise.race
|
|
5
|
-
|
|
6
|
-
See https://github.com/nodejs/node/issues/17469
|
|
7
|
-
See https://bugs.chromium.org/p/v8/issues/detail?id=9858#c9
|
|
8
|
-
|
|
1
|
+
/** Fixed Promise.race, which doesn't leak promises values. Promises still leak the Promise object themselves, but a Promise is < 100 bytes, where as the promise VALUE might be arbitrarily large.
|
|
9
2
|
*/
|
|
10
|
-
export declare function
|
|
3
|
+
export declare function PromiseRace<T extends any[]>(promises: {
|
|
4
|
+
[K in keyof T]: Promise<T[K]>;
|
|
5
|
+
}): Promise<T[number]>;
|
package/src/promiseRace.ts
CHANGED
|
@@ -1,184 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
// Less leaky promise.
|
|
3
|
-
// See https://github.com/nodejs/node/issues/17469
|
|
4
|
-
// See https://bugs.chromium.org/p/v8/issues/detail?id=9858#c9
|
|
5
|
-
// Basically, make resolve/reject weakly reference the Promise, so that
|
|
6
|
-
// resolved promises aren't kept alive. The `resolve` function is still leaked
|
|
7
|
-
// itself, but at least it doesn't leak the underlying data.
|
|
8
|
-
export class PromiseLessLeaky<T> extends Promise<T> {
|
|
9
|
-
constructor(executor: ((
|
|
10
|
-
resolve: (value: T | PromiseLike<T>) => void,
|
|
11
|
-
reject: (reason?: any) => void
|
|
12
|
-
) => void) | undefined
|
|
13
|
-
) {
|
|
14
|
-
super(((
|
|
15
|
-
resolve: ((value: T | PromiseLike<T>) => void) | undefined,
|
|
16
|
-
reject: ((reason?: any) => void) | undefined
|
|
17
|
-
) => {
|
|
18
|
-
executor?.(
|
|
19
|
-
function PromiseLessLeakyResolved(value) {
|
|
20
|
-
let callback = resolve;
|
|
21
|
-
resolve = undefined;
|
|
22
|
-
reject = undefined;
|
|
23
|
-
if (callback) {
|
|
24
|
-
callback(value);
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
function PromiseLessLeakyRejected(value) {
|
|
28
|
-
let callback = reject;
|
|
29
|
-
resolve = undefined;
|
|
30
|
-
reject = undefined;
|
|
31
|
-
if (callback) {
|
|
32
|
-
callback(value);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
);
|
|
36
|
-
executor = undefined;
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function remove<T>(list: T[], value: T) {
|
|
41
|
-
let index = list.indexOf(value);
|
|
42
|
-
if (index >= 0) {
|
|
43
|
-
list.splice(index, 1);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const promiseCallbacks = new WeakMap<object, {
|
|
47
|
-
onResolve: ((value: any) => void)[];
|
|
48
|
-
onReject: ((value: any) => void)[];
|
|
49
|
-
onFinally: (() => void)[];
|
|
50
|
-
}>();
|
|
51
|
-
/** A promise race function which doesn't leak, unlike Promise.race
|
|
52
|
-
|
|
53
|
-
See https://github.com/nodejs/node/issues/17469
|
|
54
|
-
See https://bugs.chromium.org/p/v8/issues/detail?id=9858#c9
|
|
55
|
-
|
|
1
|
+
/** Fixed Promise.race, which doesn't leak promises values. Promises still leak the Promise object themselves, but a Promise is < 100 bytes, where as the promise VALUE might be arbitrarily large.
|
|
56
2
|
*/
|
|
57
|
-
export function
|
|
58
|
-
return new PromiseLessLeaky((resolve, reject) => {
|
|
59
|
-
let actualPromises: Promise<unknown>[] = [];
|
|
3
|
+
export function PromiseRace<T extends any[]>(promises: { [K in keyof T]: Promise<T[K]> }): Promise<T[number]> {
|
|
4
|
+
return new PromiseLessLeaky((resolve: any, reject: any) => {
|
|
60
5
|
function onFinally() {
|
|
61
|
-
for (let promise of
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
6
|
+
for (let promise of promises) {
|
|
7
|
+
if (promise && typeof promise === "object" && promise instanceof Promise) {
|
|
8
|
+
let callbackObj = promiseCallbacks.get(promise);
|
|
9
|
+
if (!callbackObj) continue;
|
|
10
|
+
remove(callbackObj.onResolve, resolve);
|
|
11
|
+
remove(callbackObj.onReject, reject);
|
|
12
|
+
remove(callbackObj.onFinally, onFinally);
|
|
13
|
+
}
|
|
67
14
|
}
|
|
68
15
|
}
|
|
69
16
|
for (let promise of promises) {
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
17
|
+
// NOTE: This "if" statement greatly reduce leaks, although it might
|
|
18
|
+
// reduce speed as well?
|
|
19
|
+
if (promise && typeof promise === "object" && promise instanceof Promise) {
|
|
20
|
+
let callbackObj = promiseCallbacks.get(promise);
|
|
21
|
+
let firstSetup = false;
|
|
22
|
+
if (!callbackObj) {
|
|
23
|
+
firstSetup = true;
|
|
24
|
+
callbackObj = {
|
|
25
|
+
onResolve: [],
|
|
26
|
+
onReject: [],
|
|
27
|
+
onFinally: [],
|
|
28
|
+
};
|
|
29
|
+
promiseCallbacks.set(promise, callbackObj);
|
|
30
|
+
}
|
|
31
|
+
callbackObj.onResolve.push(resolve);
|
|
32
|
+
callbackObj.onReject.push(reject);
|
|
33
|
+
callbackObj.onFinally.push(onFinally);
|
|
34
|
+
// We need to delay this in case we're immediately triggered, because we're already resolved.
|
|
35
|
+
if (firstSetup) {
|
|
36
|
+
connectPromiseToCallbackObj(promise, callbackObj);
|
|
37
|
+
}
|
|
73
38
|
continue;
|
|
74
39
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (!callbackObj) {
|
|
78
|
-
callbackObj = {
|
|
79
|
-
onResolve: [],
|
|
80
|
-
onReject: [],
|
|
81
|
-
onFinally: [],
|
|
82
|
-
};
|
|
83
|
-
promiseCallbacks.set(promise, callbackObj);
|
|
84
|
-
connectPromiseToCallbackObj(Promise.resolve(promise), callbackObj);
|
|
85
|
-
}
|
|
86
|
-
callbackObj.onResolve.push(resolve);
|
|
87
|
-
callbackObj.onReject.push(reject);
|
|
88
|
-
callbackObj.onFinally.push(onFinally);
|
|
40
|
+
|
|
41
|
+
void Promise.resolve(promise).then(resolve, reject);
|
|
89
42
|
}
|
|
90
43
|
}) as any;
|
|
91
|
-
}
|
|
44
|
+
}
|
|
92
45
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
46
|
+
function remove(list: any, value: any) {
|
|
47
|
+
let index = list.indexOf(value);
|
|
48
|
+
if (index >= 0) {
|
|
49
|
+
list.splice(index, 1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const promiseCallbacks = new WeakMap();
|
|
53
|
+
|
|
54
|
+
function connectPromiseToCallbackObj(promise: any, callbackObj: any) {
|
|
98
55
|
// NOTE: If the promise stays alive forever... this will leak callbackObj. BUT,
|
|
99
56
|
// it is only called once per promise, ever, so... the leak isn't so bad!
|
|
100
57
|
promise.then(
|
|
101
|
-
value => {
|
|
102
|
-
|
|
58
|
+
(value: any) => {
|
|
59
|
+
// We need to delete the callback lists once on finally happens. Because we only connect to the promise once, And so it'll only trigger us once. So removing it is the only way to get it to trigger us again if someone subscribes to a finished promise.
|
|
60
|
+
promiseCallbacks.delete(promise);
|
|
61
|
+
for (let fnc of callbackObj.onResolve.slice()) {
|
|
103
62
|
try { fnc(value); } finally { }
|
|
104
63
|
}
|
|
105
64
|
},
|
|
106
|
-
value => {
|
|
107
|
-
|
|
65
|
+
(value: any) => {
|
|
66
|
+
promiseCallbacks.delete(promise);
|
|
67
|
+
for (let fnc of callbackObj.onReject.slice()) {
|
|
108
68
|
try { fnc(value); } finally { }
|
|
109
69
|
}
|
|
110
70
|
}
|
|
111
71
|
).finally(() => {
|
|
112
|
-
for (let fnc of callbackObj.onFinally) {
|
|
72
|
+
for (let fnc of callbackObj.onFinally.slice()) {
|
|
113
73
|
try { fnc(); } finally { }
|
|
114
74
|
}
|
|
115
75
|
});
|
|
116
76
|
}
|
|
117
77
|
|
|
118
78
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
79
|
+
// Less leaky promise.
|
|
80
|
+
// See https://github.com/nodejs/node/issues/17469
|
|
81
|
+
// See https://bugs.chromium.org/p/v8/issues/detail?id=9858#c9
|
|
82
|
+
// Basically, make resolve/reject weakly reference the Promise, so that
|
|
83
|
+
// resolved promises aren't kept alive. The `resolve` function is still leaked
|
|
84
|
+
// itself, but at least it doesn't leak the underlying data.
|
|
85
|
+
// IMPORTANT! This still leaks! So... maybe don't even use Promise.race?
|
|
86
|
+
class PromiseLessLeaky extends Promise<any> {
|
|
87
|
+
constructor(executor: any) {
|
|
88
|
+
super((resolve, reject) => {
|
|
89
|
+
executor(
|
|
90
|
+
function PromiseLessLeakyResolved(value: any) {
|
|
91
|
+
let callback = resolve;
|
|
92
|
+
resolve = undefined as any;
|
|
93
|
+
reject = undefined as any;
|
|
94
|
+
if (callback) {
|
|
95
|
+
callback(value);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
function PromiseLessLeakyRejected(value: any) {
|
|
99
|
+
let callback = reject;
|
|
100
|
+
resolve = undefined as any;
|
|
101
|
+
reject = undefined as any;
|
|
102
|
+
if (callback) {
|
|
103
|
+
callback(value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
executor = undefined;
|
|
147
108
|
});
|
|
148
|
-
return {
|
|
149
|
-
promise,
|
|
150
|
-
resolve,
|
|
151
|
-
reject,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
let exitPromiseObj = createPromiseObj<void>();
|
|
156
|
-
setTimeout(() => {
|
|
157
|
-
exitPromiseObj.resolve(undefined);
|
|
158
|
-
}, 1000 * 60 * 60);
|
|
159
|
-
|
|
160
|
-
async function doWork() {
|
|
161
|
-
return new LeakedTag();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// @ts-ignore
|
|
165
|
-
let createRace: (promises: Promise<any>[]) => Promise<any>;
|
|
166
|
-
|
|
167
|
-
// Leaks (you can see LeakedTag in the heap dump, and also usage goes to the moon)
|
|
168
|
-
//createRace = promises => Promise.race(promises);
|
|
169
|
-
|
|
170
|
-
// Doesn't leak
|
|
171
|
-
createRace = promises => promiseRace(promises);
|
|
172
|
-
|
|
173
|
-
let time = Date.now();
|
|
174
|
-
|
|
175
|
-
while (true) {
|
|
176
|
-
await createRace([doWork(), exitPromiseObj.promise]);
|
|
177
|
-
count++;
|
|
178
|
-
if (count % 100000 === 0) {
|
|
179
|
-
let duration = Date.now() - time;
|
|
180
|
-
console.log(`Did ${count} test runs, used memory is at ${(process.memoryUsage().heapUsed)}, time per count is ${formatTime(duration / count)}`);
|
|
181
|
-
}
|
|
182
109
|
}
|
|
183
110
|
}
|
|
184
|
-
//void main();
|
package/src/webSocketServer.ts
CHANGED
|
@@ -5,7 +5,7 @@ import tls from "tls";
|
|
|
5
5
|
import { getNodeIdsFromRequest, httpCallHandler } from "./callHTTPHandler";
|
|
6
6
|
import { SocketFunction } from "../SocketFunction";
|
|
7
7
|
import { getTrustedCertificates, watchTrustedCertificates } from "./certStore";
|
|
8
|
-
import { createCallFactory
|
|
8
|
+
import { createCallFactory } from "./CallFactory";
|
|
9
9
|
import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
|
|
10
10
|
import debugbreak from "debugbreak";
|
|
11
11
|
import { getNodeId } from "./nodeCache";
|
|
@@ -351,7 +351,6 @@ export async function startSocketServer(
|
|
|
351
351
|
|
|
352
352
|
let nodeId = getNodeId(getCommonName(config.cert), port);
|
|
353
353
|
console.log(green(`Started Listening on ${nodeId} (${host}) after ${formatTime(process.uptime() * 1000)}`));
|
|
354
|
-
registerOwnReconnectionUrl(nodeId);
|
|
355
354
|
|
|
356
355
|
return nodeId;
|
|
357
356
|
}
|