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 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
- export declare class PromiseLessLeaky<T> extends Promise<T> {
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 promiseRace<T extends readonly unknown[] | []>(promises: T): Promise<Awaited<T[number]>>;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "1.1.19",
3
+ "version": "1.1.20",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -1,10 +1,5 @@
1
- export declare class PromiseLessLeaky<T> extends Promise<T> {
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 promiseRace<T extends readonly unknown[] | []>(promises: T): Promise<Awaited<T[number]>>;
3
+ export declare function PromiseRace<T extends any[]>(promises: {
4
+ [K in keyof T]: Promise<T[K]>;
5
+ }): Promise<T[number]>;
@@ -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 promiseRace<T extends readonly unknown[] | []>(promises: T): Promise<Awaited<T[number]>> {
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 actualPromises) {
62
- let callbackObj = promiseCallbacks.get(promise);
63
- if (!callbackObj) continue;
64
- remove(callbackObj.onResolve, resolve);
65
- remove(callbackObj.onReject, reject);
66
- remove(callbackObj.onFinally, onFinally);
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
- // If not a thenable, use Promise.resolve to make it a promise, and use the build in functions, as it won't hold a reference because it will resolve immediately.
71
- if (!(promise && (typeof promise === "object" || typeof promise === "function") && ("then" in promise))) {
72
- Promise.resolve(promise).then(resolve as any, reject);
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
- actualPromises.push(promise as Promise<unknown>);
76
- let callbackObj = promiseCallbacks.get(promise);
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 connectPromiseToCallbackObj(promise: Promise<any>, callbackObj: {
94
- onResolve: ((value: any) => void)[];
95
- onReject: ((value: any) => void)[];
96
- onFinally: (() => void)[];
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
- for (let fnc of callbackObj.onResolve) {
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
- for (let fnc of callbackObj.onReject) {
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
- async function main() {
120
- const { formatTime } = await import("./formatting/format");
121
- const os = require("os");
122
- let count = 0;
123
-
124
- // @ts-ignore
125
- function createNamedObject(name) {
126
- return eval(`(class ${name} {
127
- memory = (() => {
128
- // let memory = new Float64Array(1024 * 1024);
129
- // for (let i = 0; i < memory.length; i++) {
130
- // memory[i] = Math.random();
131
- // }
132
- // return memory;
133
- })();
134
- })`);
135
- }
136
- let LeakedTag = createNamedObject("LeakedTag");
137
- let LeakedTag2 = createNamedObject("LeakedTag2");
138
- function createPromiseObj<T>() {
139
- let resolve!: ((value: T | PromiseLike<T>) => void);
140
- let reject!: ((reason?: any) => void);
141
- let promise = new Promise<T>((_resolve, _reject) => {
142
- resolve = _resolve;
143
- reject = _reject;
144
- });
145
- void promise.finally(() => {
146
- (globalThis as any)["keepAliveValueTest"] = ((globalThis as any)["keepAliveValueTest"] || 0) + 1;
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();