socket-function 1.1.19 → 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/index.d.ts +4 -9
- package/package.json +1 -1
- package/src/promiseRace.d.ts +4 -9
- package/src/promiseRace.ts +81 -155
package/index.d.ts
CHANGED
|
@@ -1265,16 +1265,11 @@ declare module "socket-function/src/profiling/tcpLagProxy" {
|
|
|
1265
1265
|
}
|
|
1266
1266
|
|
|
1267
1267
|
declare module "socket-function/src/promiseRace" {
|
|
1268
|
-
|
|
1269
|
-
constructor(executor: ((resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) | undefined);
|
|
1270
|
-
}
|
|
1271
|
-
/** A promise race function which doesn't leak, unlike Promise.race
|
|
1272
|
-
|
|
1273
|
-
See https://github.com/nodejs/node/issues/17469
|
|
1274
|
-
See https://bugs.chromium.org/p/v8/issues/detail?id=9858#c9
|
|
1275
|
-
|
|
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.
|
|
1276
1269
|
*/
|
|
1277
|
-
export declare function
|
|
1270
|
+
export declare function PromiseRace<T extends any[]>(promises: {
|
|
1271
|
+
[K in keyof T]: Promise<T[K]>;
|
|
1272
|
+
}): Promise<T[number]>;
|
|
1278
1273
|
|
|
1279
1274
|
}
|
|
1280
1275
|
|
package/package.json
CHANGED
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();
|