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 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, registerOwnReconnectionUrl } from "./src/CallFactory";
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
- export declare class PromiseLessLeaky<T> extends Promise<T> {
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 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]>;
1280
1273
 
1281
1274
  }
1282
1275
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -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 {};
@@ -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
- // NOTE: This used to default to empty string, which would be the case if we made the connection. But this caused problems when we want it to stop making two connections per server and reuse the connection if both the servers want it to talk to each other. I guess we can default it to our reconnection URL. I mean, how else are people going to connect to us other than that?
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;
@@ -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();
@@ -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, registerOwnReconnectionUrl } from "./CallFactory";
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
  }