socket-function 0.135.0 → 0.137.0

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
@@ -26,10 +26,10 @@ if (isNode()) {
26
26
  // not a good alternative to proper error log and notifications. Do you guys
27
27
  // not get automated emails when unexpected errors are logged? I do.
28
28
  process.on("unhandledRejection", (e) => {
29
- console.error("Unhandled rejection", e);
29
+ console.error("Unhandled rejection" + ((e as any)?.stack || e));
30
30
  });
31
31
  process.on("uncaughtException", (e) => {
32
- console.error("Uncaught exception", e);
32
+ console.error("Uncaught exception" + ((e as any)?.stack || e));
33
33
  });
34
34
  }
35
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.135.0",
3
+ "version": "0.137.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
package/src/https.ts CHANGED
@@ -12,6 +12,7 @@ export function httpsRequest(
12
12
  sendSessionCookies = true,
13
13
  config?: {
14
14
  headers?: { [key: string]: string | undefined },
15
+ cancel?: Promise<void>;
15
16
  }
16
17
  ): Promise<Buffer> {
17
18
  if (isNode()) {
@@ -52,6 +53,11 @@ export function httpsRequest(
52
53
  }
53
54
  }
54
55
  );
56
+ if (config?.cancel) {
57
+ void config.cancel.finally(() => {
58
+ httpRequest.destroy();
59
+ });
60
+ }
55
61
  httpRequest.on("error", reject);
56
62
 
57
63
  if (payload) {
@@ -73,6 +79,11 @@ export function httpsRequest(
73
79
  request.setRequestHeader(key, value);
74
80
  }
75
81
  }
82
+ if (config?.cancel) {
83
+ void config.cancel.finally(() => {
84
+ request.abort();
85
+ });
86
+ }
76
87
  request.responseType = "arraybuffer";
77
88
  request.withCredentials = sendSessionCookies;
78
89
  if (payload) {
@@ -85,7 +96,7 @@ export function httpsRequest(
85
96
  }
86
97
  return new Promise((resolve, reject) => {
87
98
  request.onload = () => {
88
- if (request.status !== 200) {
99
+ if (!request.status.toString().startsWith("2")) {
89
100
  try {
90
101
  // It should be an error.stack. But if it isn't... just throw the status text...
91
102
  let responseText = textDecoder.decode(request.response);
@@ -40,11 +40,11 @@ const noDiskLogPrefix = "█ ";
40
40
  export function measureFnc(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
41
41
  let name = propertyKey;
42
42
  if (target.name) {
43
- name = `${target.name}.${name}`;
43
+ name = `${target.name}|${name}`;
44
44
  } else {
45
45
  let constructorName = target.constructor.name;
46
46
  if (constructorName) {
47
- name = `${constructorName}().${name}`;
47
+ name = `${constructorName}()|${name}`;
48
48
  }
49
49
  }
50
50
  if (descriptor.value instanceof AsyncFunction) {
@@ -0,0 +1,184 @@
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
+
56
+ */
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>[] = [];
60
+ 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);
67
+ }
68
+ }
69
+ 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);
73
+ continue;
74
+ }
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);
89
+ }
90
+ }) as any;
91
+ };
92
+
93
+ function connectPromiseToCallbackObj(promise: Promise<any>, callbackObj: {
94
+ onResolve: ((value: any) => void)[];
95
+ onReject: ((value: any) => void)[];
96
+ onFinally: (() => void)[];
97
+ }) {
98
+ // NOTE: If the promise stays alive forever... this will leak callbackObj. BUT,
99
+ // it is only called once per promise, ever, so... the leak isn't so bad!
100
+ promise.then(
101
+ value => {
102
+ for (let fnc of callbackObj.onResolve) {
103
+ try { fnc(value); } finally { }
104
+ }
105
+ },
106
+ value => {
107
+ for (let fnc of callbackObj.onReject) {
108
+ try { fnc(value); } finally { }
109
+ }
110
+ }
111
+ ).finally(() => {
112
+ for (let fnc of callbackObj.onFinally) {
113
+ try { fnc(); } finally { }
114
+ }
115
+ });
116
+ }
117
+
118
+
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;
147
+ });
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
+ }
183
+ }
184
+ //void main();
@@ -98,8 +98,7 @@ export async function startSocketServer(
98
98
  }
99
99
 
100
100
  watchTrustedCertificates(() => {
101
- // NOTE: If this is called a lot... STOP CALLING IT A LOT! Calling setSecureContext
102
- // so frequently likely leaks memory!
101
+ // NOTE: If this is called a lot... STOP CALLING IT A LOT! Calling setSecureContext frequently leaks memory! (As in, once a minute is maybe too much, once a second is definitely too much)
103
102
  console.log(`Updating websocket server trusted certificates`);
104
103
  lastOptions.ca = getTrustedCertificates();
105
104
  httpsServer.setSecureContext(lastOptions);