socket-function 0.135.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 +1 -1
- package/src/https.ts +11 -0
- package/src/promiseRace.ts +184 -0
package/package.json
CHANGED
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();
|