socket-function 0.134.0 → 0.136.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.134.0",
3
+ "version": "0.136.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -480,6 +480,10 @@ export function requireMain() {
480
480
 
481
481
  function createRequire(module: ModuleType, serializedModule: SerializedModule, asyncIsFineOuter?: boolean) {
482
482
  require.cache = moduleCache;
483
+ // Dynamically get folder, incase our filename changes
484
+ function getModuleFolder() {
485
+ return module.filename.replace(/\\/g, "/").split("/").slice(0, -1).join("/") + "/";
486
+ }
483
487
  function resolve(request: string) {
484
488
  let requests = serializedModule.requests;
485
489
  if (request in requests) {
@@ -487,7 +491,7 @@ export function requireMain() {
487
491
  }
488
492
  let absolutePath = request;
489
493
  if (absolutePath.startsWith("./") || absolutePath.startsWith("../")) {
490
- let folderParts = moduleFolder.split("/");
494
+ let folderParts = getModuleFolder().split("/");
491
495
  while (absolutePath.startsWith("./") || absolutePath.startsWith("../")) {
492
496
  if (absolutePath.startsWith("./")) {
493
497
  absolutePath = absolutePath.slice("./".length);
@@ -505,7 +509,6 @@ export function requireMain() {
505
509
  return absolutePath;
506
510
  }
507
511
  require.resolve = resolve;
508
- let moduleFolder = module.filename.replace(/\\/g, "/").split("/").slice(0, -1).join("/") + "/";
509
512
  return require;
510
513
  function require(request: string, asyncIsFine?: boolean) {
511
514
  if (asyncIsFineOuter) {
@@ -792,6 +795,7 @@ export function requireMain() {
792
795
  },
793
796
  },
794
797
  module.exports,
798
+ // eslint-disable-next-line @typescript-eslint/unbound-method
795
799
  module.require,
796
800
  module,
797
801
  module.filename,
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) {
@@ -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();