socket-function 1.1.3 → 1.1.5
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.d.ts +1 -0
- package/SocketFunction.ts +5 -2
- package/SocketFunctionTypes.d.ts +1 -1
- package/SocketFunctionTypes.ts +2 -1
- package/index.d.ts +84 -3
- package/package.json +1 -1
- package/src/CallFactory.d.ts +5 -0
- package/src/CallFactory.ts +261 -94
- package/src/batching.d.ts +8 -1
- package/src/batching.ts +37 -0
- package/src/callManager.d.ts +1 -1
- package/src/callManager.ts +1 -1
- package/src/lz4/LZ4.d.ts +7 -0
- package/src/lz4/LZ4.ts +32 -0
- package/src/lz4/lz4_wasm_nodejs.d.ts +34 -0
- package/src/lz4/lz4_wasm_nodejs.js +178 -0
- package/src/lz4/lz4_wasm_nodejs_bg.js +94 -0
- package/src/lz4/lz4_wasm_nodejs_bg.wasm +0 -0
- package/src/lz4/lz4_wasm_nodejs_bg.wasm.d.ts +15 -0
package/src/CallFactory.ts
CHANGED
|
@@ -8,7 +8,7 @@ import * as tls from "tls";
|
|
|
8
8
|
import { getClientNodeId, getNodeIdLocation, registerNodeClient } from "./nodeCache";
|
|
9
9
|
import debugbreak from "debugbreak";
|
|
10
10
|
import { lazy } from "./caching";
|
|
11
|
-
import { red, yellow } from "./formatting/logColors";
|
|
11
|
+
import { blue, green, red, yellow } from "./formatting/logColors";
|
|
12
12
|
import { isSplitableArray, markArrayAsSplitable } from "./fixLargeNetworkCalls";
|
|
13
13
|
import { delay, runInfinitePoll, runInSerial } from "./batching";
|
|
14
14
|
import { formatNumber, formatTime } from "./formatting/format";
|
|
@@ -18,6 +18,10 @@ import { setFlag } from "../require/compileFlags";
|
|
|
18
18
|
import { measureFnc, measureWrap, registerMeasureInfo } from "./profiling/measure";
|
|
19
19
|
import { MaybePromise } from "./types";
|
|
20
20
|
import { Zip } from "./Zip";
|
|
21
|
+
import { LZ4 } from "./lz4/LZ4";
|
|
22
|
+
//LZ4.compress;
|
|
23
|
+
//LZ4.decompress;
|
|
24
|
+
|
|
21
25
|
setFlag(require, "pako", "allowclient", true);
|
|
22
26
|
|
|
23
27
|
// NOTE: If it is too low, and too many servers disconnect, we can easily spend 100% of our time
|
|
@@ -28,7 +32,7 @@ const MIN_RETRY_DELAY = 5000;
|
|
|
28
32
|
type InternalCallType = FullCallType & {
|
|
29
33
|
seqNum: number;
|
|
30
34
|
isReturn: false;
|
|
31
|
-
isArgsCompressed?: boolean;
|
|
35
|
+
isArgsCompressed?: boolean | "LZ4" | "zip";
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
type InternalReturnType = {
|
|
@@ -36,7 +40,7 @@ type InternalReturnType = {
|
|
|
36
40
|
result: unknown;
|
|
37
41
|
error?: string;
|
|
38
42
|
seqNum: number;
|
|
39
|
-
isResultCompressed?: boolean;
|
|
43
|
+
isResultCompressed?: boolean | "LZ4" | "zip";
|
|
40
44
|
};
|
|
41
45
|
|
|
42
46
|
|
|
@@ -45,6 +49,7 @@ export interface CallFactory {
|
|
|
45
49
|
lastClosed: number;
|
|
46
50
|
closedForever?: boolean;
|
|
47
51
|
isConnected?: boolean;
|
|
52
|
+
receivedInitializeState?: InitializeState;
|
|
48
53
|
// NOTE: May or may not have reconnection or retry logic inside of performCall.
|
|
49
54
|
// Trigger performLocalCall on the other side of the connection
|
|
50
55
|
performCall(call: CallType): Promise<unknown>;
|
|
@@ -69,6 +74,12 @@ export interface SenderInterface {
|
|
|
69
74
|
ping?(): void;
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
type InitializeState = {
|
|
78
|
+
supportsLZ4?: boolean;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const INITIALIZE_STATE_SEQ_NUM = -1;
|
|
82
|
+
|
|
72
83
|
let pendingCallCount = 0;
|
|
73
84
|
let harvestableFailedCalls = 0;
|
|
74
85
|
const CALL_TIMES_LIMIT = 1000 * 1000 * 10;
|
|
@@ -145,6 +156,7 @@ export async function createCallFactory(
|
|
|
145
156
|
nodeId,
|
|
146
157
|
lastClosed: 0,
|
|
147
158
|
connectionId: { nodeId },
|
|
159
|
+
receivedInitializeState: undefined,
|
|
148
160
|
onNextDisconnect,
|
|
149
161
|
async performCall(call: CallType) {
|
|
150
162
|
let seqNum = nextSeqNum++;
|
|
@@ -164,9 +176,18 @@ export async function createCallFactory(
|
|
|
164
176
|
compressedSize: 0,
|
|
165
177
|
};
|
|
166
178
|
try {
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
179
|
+
if (callFactory.receivedInitializeState?.supportsLZ4) {
|
|
180
|
+
let compressMode = shouldCompressCall(fullCall);
|
|
181
|
+
// If it's undefined, then we compress it. We basically always want to compress from now on, because LZ4 is so fast.
|
|
182
|
+
if (compressMode !== false) {
|
|
183
|
+
fullCall.args = await compressObjLZ4(fullCall.args, sendStats) as any;
|
|
184
|
+
fullCall.isArgsCompressed = "LZ4";
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
if (shouldCompressCall(fullCall)) {
|
|
188
|
+
fullCall.args = await compressObj(fullCall.args, sendStats) as any;
|
|
189
|
+
fullCall.isArgsCompressed = "zip";
|
|
190
|
+
}
|
|
170
191
|
}
|
|
171
192
|
let dataMaybePromise = SocketFunction.WIRE_SERIALIZER.serialize(fullCall);
|
|
172
193
|
if (dataMaybePromise instanceof Promise) {
|
|
@@ -248,7 +269,7 @@ export async function createCallFactory(
|
|
|
248
269
|
let arg = originalArgs[0] as any;
|
|
249
270
|
fncHack = `.${arg.DomainName}.${arg.ModuleId}.${arg.FunctionId}`;
|
|
250
271
|
}
|
|
251
|
-
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) +
|
|
272
|
+
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + `B`).padEnd(4, " ")}\t${formatNumber(data.length)} buffers\tREMOTE CALL\t${call.classGuid}.${call.functionName}${fncHack} at ${Date.now()}`);
|
|
252
273
|
}
|
|
253
274
|
}
|
|
254
275
|
// If sending OR resultPromise throws, we want to error out. This solves some issues with resultPromise
|
|
@@ -268,6 +289,7 @@ export async function createCallFactory(
|
|
|
268
289
|
|
|
269
290
|
async function initializeWebsocket(newWebSocket: SenderInterface) {
|
|
270
291
|
registerOnce();
|
|
292
|
+
callFactory.receivedInitializeState = undefined;
|
|
271
293
|
|
|
272
294
|
function onClose(error: string) {
|
|
273
295
|
callFactory.connectionId = { nodeId };
|
|
@@ -427,6 +449,7 @@ export async function createCallFactory(
|
|
|
427
449
|
|
|
428
450
|
let pendingCall: MessageHeader & {
|
|
429
451
|
buffers: Buffer[];
|
|
452
|
+
firstReceivedTime?: number;
|
|
430
453
|
} | undefined;
|
|
431
454
|
|
|
432
455
|
async function processPendingCall() {
|
|
@@ -499,101 +522,37 @@ export async function createCallFactory(
|
|
|
499
522
|
};
|
|
500
523
|
|
|
501
524
|
if (call.isReturn) {
|
|
525
|
+
if (!SocketFunction.LEGACY_INITIALIZE && call.seqNum === INITIALIZE_STATE_SEQ_NUM) {
|
|
526
|
+
callFactory.receivedInitializeState = call.result as InitializeState;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
502
529
|
let callbackObj = pendingCalls.get(call.seqNum);
|
|
503
530
|
if (parseTime > SocketFunction.WIRE_WARN_TIME) {
|
|
504
531
|
console.log(red(`Slow parse, took ${parseTime}ms to parse ${resultSize} bytes, for receiving result of call to ${callbackObj?.call.classGuid}.${callbackObj?.call.functionName}`));
|
|
505
532
|
}
|
|
506
533
|
if (!callbackObj) {
|
|
507
|
-
console.log(`Got return for unknown call ${call.seqNum} (created at time ${new Date(call.seqNum)})`);
|
|
534
|
+
console.log(blue(`Got return for unknown call ${call.seqNum} (created at time ${new Date(call.seqNum)}), ${nodeId} / ${localNodeId}`));
|
|
508
535
|
return;
|
|
509
536
|
}
|
|
510
537
|
if (SocketFunction.logMessages) {
|
|
511
538
|
let call = callbackObj.call;
|
|
512
|
-
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\tRETURN\t${call.classGuid}.${call.functionName} at ${Date.now()}, (${nodeId} / ${localNodeId})`);
|
|
539
|
+
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\t${formatNumber(currentBuffers.length)} buffers\tRETURN\t${call.classGuid}.${call.functionName} at ${Date.now()}, (${nodeId} / ${localNodeId})`);
|
|
513
540
|
}
|
|
514
|
-
if (call.isResultCompressed) {
|
|
541
|
+
if (call.isResultCompressed === "LZ4") {
|
|
542
|
+
call.result = await decompressObjLZ4(call.result as Buffer[], receiveStats);
|
|
543
|
+
call.isResultCompressed = undefined;
|
|
544
|
+
} else if (call.isResultCompressed === "zip" || call.isResultCompressed === true) {
|
|
515
545
|
call.result = await decompressObj(call.result as Buffer, receiveStats);
|
|
516
|
-
call.isResultCompressed =
|
|
546
|
+
call.isResultCompressed = undefined;
|
|
517
547
|
}
|
|
518
548
|
callbackObj.callback(call);
|
|
519
549
|
} else {
|
|
520
|
-
if (call.isArgsCompressed) {
|
|
550
|
+
if (call.isArgsCompressed === "LZ4") {
|
|
551
|
+
call.args = await decompressObjLZ4(call.args as any as Buffer[], sendStats) as any;
|
|
552
|
+
call.isArgsCompressed = undefined;
|
|
553
|
+
} else if (call.isArgsCompressed === "zip" || call.isArgsCompressed === true) {
|
|
521
554
|
call.args = await decompressObj(call.args as any as Buffer, sendStats) as any;
|
|
522
|
-
call.isArgsCompressed =
|
|
523
|
-
}
|
|
524
|
-
if (call.functionName === "changeIdentity") {
|
|
525
|
-
/*
|
|
526
|
-
TODO: Sometimes calls don't get through, even though we know the client made the call. Here are the logs from a failing case:
|
|
527
|
-
Exposing Controller ServerController-17ea53da-bbef-4c8b-9eb0-99e263464c6f
|
|
528
|
-
Exposing Controller HotReloadController-032b2250-3aac-4187-8c95-75412742b8f5
|
|
529
|
-
Exposing Controller TimeController-ddf4753e-fc8a-413f-8cc2-b927dd449976
|
|
530
|
-
Updating websocket server options
|
|
531
|
-
Updating websocket server trusted certificates
|
|
532
|
-
Updating websocket server options
|
|
533
|
-
Updating websocket server trusted certificates
|
|
534
|
-
Updating websocket server options
|
|
535
|
-
Updating websocket server trusted certificates
|
|
536
|
-
Trying to listening on 127.0.0.1:4231
|
|
537
|
-
Started Listening on planquickly.com:4231 (127.0.0.1) after 5.54s
|
|
538
|
-
Mounted on 127-0-0-1.planquickly.com:4231
|
|
539
|
-
Exposing Controller RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d
|
|
540
|
-
Received TCP connection from 127.0.0.1:42105
|
|
541
|
-
Received TCP header packet from 127.0.0.1:42105, have 1894 bytes so far, 1 packets
|
|
542
|
-
Received TCP connection with SNI "127-0-0-1.planquickly.com". Have handlers for: planquickly.com, 127-0-0-1.planquickly.com
|
|
543
|
-
HTTP server connection established 127.0.0.1:42105
|
|
544
|
-
HTTP request (GET) https://127-0-0-1.planquickly.com:4231/?hot
|
|
545
|
-
HTTP response 106KB (GET) https://127-0-0-1.planquickly.com:4231/?hot
|
|
546
|
-
HTTP server socket closed for 127.0.0.1:42105
|
|
547
|
-
Received TCP connection from 127.0.0.1:42106
|
|
548
|
-
Received TCP header packet from 127.0.0.1:42106, have 1862 bytes so far, 1 packets
|
|
549
|
-
Received TCP connection with SNI "127-0-0-1.planquickly.com". Have handlers for: planquickly.com, 127-0-0-1.planquickly.com
|
|
550
|
-
HTTP server connection established 127.0.0.1:42106
|
|
551
|
-
HTTP request (GET) https://127-0-0-1.planquickly.com:4231/?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=getModules&args=%5B%5B%22.%2Fsite%2FsiteMain%22%5D%2Cnull%5D
|
|
552
|
-
HTTP response 10.8MB (GET) https://127-0-0-1.planquickly.com:4231/?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=getModules&args=%5B%5B%22.%2Fsite%2FsiteMain%22%5D%2Cnull%5D
|
|
553
|
-
Received TCP connection from 127.0.0.1:42107
|
|
554
|
-
Received TCP header packet from 127.0.0.1:42107, have 1894 bytes so far, 1 packets
|
|
555
|
-
Received TCP connection with SNI "127-0-0-1.planquickly.com". Have handlers for: planquickly.com, 127-0-0-1.planquickly.com
|
|
556
|
-
HTTP server connection established 127.0.0.1:42107
|
|
557
|
-
HTTP server socket closed for 127.0.0.1:42106
|
|
558
|
-
HTTP server socket closed for 127.0.0.1:42107
|
|
559
|
-
Received TCP connection from 127.0.0.1:42108
|
|
560
|
-
Received TCP header packet from 127.0.0.1:42108, have 1830 bytes so far, 1 packets
|
|
561
|
-
Received TCP connection with SNI "127-0-0-1.planquickly.com". Have handlers for: planquickly.com, 127-0-0-1.planquickly.com
|
|
562
|
-
HTTP server connection established 127.0.0.1:42108
|
|
563
|
-
HTTP request (GET) https://127-0-0-1.planquickly.com:4231/node.cjs.map
|
|
564
|
-
HTTP response 106KB (GET) https://127-0-0-1.planquickly.com:4231/node.cjs.map
|
|
565
|
-
HTTP server socket closed for 127.0.0.1:42108
|
|
566
|
-
Received TCP connection from 127.0.0.1:42110
|
|
567
|
-
Received TCP header packet from 127.0.0.1:42110, have 1818 bytes so far, 1 packets
|
|
568
|
-
Received TCP connection with SNI "127-0-0-1.planquickly.com". Have handlers for: planquickly.com, 127-0-0-1.planquickly.com
|
|
569
|
-
HTTP server connection established 127.0.0.1:42110
|
|
570
|
-
Received TCP connection from 127.0.0.1:42111
|
|
571
|
-
Received TCP header packet from 127.0.0.1:42111, have 1830 bytes so far, 1 packets
|
|
572
|
-
Received TCP connection with SNI "127-0-0-1.planquickly.com". Have handlers for: planquickly.com, 127-0-0-1.planquickly.com
|
|
573
|
-
HTTP server connection established 127.0.0.1:42111
|
|
574
|
-
Received websocket upgrade request for 127.0.0.1:42110
|
|
575
|
-
Connection established to client:127.0.0.1:1744150129862.296:0.4118126921519041
|
|
576
|
-
HTTP request (GET) https://127-0-0-1.planquickly.com:4231/?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=getModules&args=%5B%5B%22D%3A%2Frepos%2Fperspectanalytics%2Fai3%2Fnode_modules%2Fsocket-function%2Ftime%2FtrueTimeShim.ts%22%5D%2C%7B%22requireSeqNumProcessId%22%3A%22requireSeqNumProcessId_1744150120269_0.5550074391586426%22%2C%22seqNumRanges%22%3A%5B%7B%22s%22%3A1%2C%22e%22%3A892%7D%5D%7D%5D
|
|
577
|
-
HTTP response 31.1KB (GET) https://127-0-0-1.planquickly.com:4231/?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=getModules&args=%5B%5B%22D%3A%2Frepos%2Fperspectanalytics%2Fai3%2Fnode_modules%2Fsocket-function%2Ftime%2FtrueTimeShim.ts%22%5D%2C%7B%22requireSeqNumProcessId%22%3A%22requireSeqNumProcessId_1744150120269_0.5550074391586426%22%2C%22seqNumRanges%22%3A%5B%7B%22s%22%3A1%2C%22e%22%3A892%7D%5D%7D%5D
|
|
578
|
-
SIZE 171B EVALUATE HotReloadController-032b2250-3aac-4187-8c95-75412742b8f5.watchFiles at 1744150129869.296
|
|
579
|
-
SIZE 174B EVALUATE ServerController-17ea53da-bbef-4c8b-9eb0-99e263464c6f.testSiteFunction at 1744150129872.296
|
|
580
|
-
HTTP server socket closed for 127.0.0.1:42111
|
|
581
|
-
SIZE 167B EVALUATE TimeController-ddf4753e-fc8a-413f-8cc2-b927dd449976.getTrueTime at 1744150129893.296
|
|
582
|
-
SIZE 167B EVALUATE TimeController-ddf4753e-fc8a-413f-8cc2-b927dd449976.getTrueTime at 1744150129897.296
|
|
583
|
-
SIZE 167B EVALUATE TimeController-ddf4753e-fc8a-413f-8cc2-b927dd449976.getTrueTime at 1744150129899.296
|
|
584
|
-
SIZE 167B EVALUATE TimeController-ddf4753e-fc8a-413f-8cc2-b927dd449976.getTrueTime at 1744150139907.0776
|
|
585
|
-
SIZE 167B EVALUATE TimeController-ddf4753e-fc8a-413f-8cc2-b927dd449976.getTrueTime at 1744150139909.0776
|
|
586
|
-
SIZE 167B EVALUATE TimeController-ddf4753e-fc8a-413f-8cc2-b927dd449976.getTrueTime at 1744150139911.0776
|
|
587
|
-
Hot reloading due to change: D:/repos/perspectanalytics/ai3/node_modules/socket-function/src/webSocketServer.ts
|
|
588
|
-
- The upgrade request finishes, at least once: Received websocket upgrade
|
|
589
|
-
- AND, we are receiving some calls, so... that appears to work.
|
|
590
|
-
- Maybe the time calls never finish?
|
|
591
|
-
- We added logging for when calls finish as well, so we can tell if all the TimeController calls timed out
|
|
592
|
-
- ALSO, added more logging to see if the calls were from the same client (which WOULD be a bug, because
|
|
593
|
-
the client shouldn't be calling us so often), or, different clients.
|
|
594
|
-
- We DO receive more connections than http connections closed. But not that many more...
|
|
595
|
-
*/
|
|
596
|
-
console.log(red(`Call to ${call.classGuid}.${call.functionName} at ${Date.now()}`));
|
|
555
|
+
call.isArgsCompressed = undefined;
|
|
597
556
|
}
|
|
598
557
|
if (SocketFunction.logMessages) {
|
|
599
558
|
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\tEVALUATE\t${call.classGuid}.${call.functionName} at ${Date.now()}, (${nodeId} / ${localNodeId})`);
|
|
@@ -614,9 +573,17 @@ export async function createCallFactory(
|
|
|
614
573
|
let timeTaken = Date.now() - time;
|
|
615
574
|
console.log(`DUR\t${(formatTime(timeTaken)).padEnd(6, " ")}\tFINISH\t${call.classGuid}.${call.functionName} at ${Date.now()}, (${nodeId} / ${localNodeId})`);
|
|
616
575
|
}
|
|
617
|
-
if (
|
|
618
|
-
|
|
619
|
-
|
|
576
|
+
if (callFactory.receivedInitializeState?.supportsLZ4) {
|
|
577
|
+
let compressMode = shouldCompressCall(call);
|
|
578
|
+
if (compressMode !== false) {
|
|
579
|
+
response.result = await compressObjLZ4(response.result, sendStats);
|
|
580
|
+
response.isResultCompressed = "LZ4";
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
if (shouldCompressCall(call)) {
|
|
584
|
+
response.result = await compressObj(response.result, sendStats);
|
|
585
|
+
response.isResultCompressed = "zip";
|
|
586
|
+
}
|
|
620
587
|
}
|
|
621
588
|
} catch (e: any) {
|
|
622
589
|
response = {
|
|
@@ -726,11 +693,19 @@ export async function createCallFactory(
|
|
|
726
693
|
return;
|
|
727
694
|
}
|
|
728
695
|
if (message instanceof Buffer) {
|
|
729
|
-
if (message.byteLength > 1000 * 1000 * 10) {
|
|
730
|
-
console.log(`Received large packet ${formatNumber(message.byteLength)}B at ${Date.now()}`);
|
|
731
|
-
}
|
|
732
696
|
if (!pendingCall) {
|
|
733
|
-
throw new Error(`Received data without size`);
|
|
697
|
+
throw new Error(`Received data without size ${message.byteLength}B, first 100 bytes: ${message.slice(0, 100).toString("hex")}`);
|
|
698
|
+
}
|
|
699
|
+
if (message.byteLength > 1000 * 1000 * 10 || pendingCall.bufferCount > 1000 && (pendingCall.buffers.length % 100 === 0)) {
|
|
700
|
+
if (pendingCall.buffers.length === 0) {
|
|
701
|
+
console.log(`Received large/many packets ${formatNumber(message.byteLength)}B (${pendingCall.buffers.length} / ${pendingCall.bufferCount}) at ${Date.now()}`);
|
|
702
|
+
} else {
|
|
703
|
+
let elapsed = Date.now() - (pendingCall.firstReceivedTime || 0);
|
|
704
|
+
console.log(`Received large/many packets ${formatNumber(message.byteLength)}B (${pendingCall.buffers.length} / ${pendingCall.bufferCount}) after ${formatTime(elapsed)}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (pendingCall.buffers.length === 0) {
|
|
708
|
+
pendingCall.firstReceivedTime = Date.now();
|
|
734
709
|
}
|
|
735
710
|
pendingCall.buffers.push(message);
|
|
736
711
|
if (pendingCall.buffers.length !== pendingCall.bufferCount) {
|
|
@@ -755,6 +730,20 @@ export async function createCallFactory(
|
|
|
755
730
|
}
|
|
756
731
|
}
|
|
757
732
|
|
|
733
|
+
|
|
734
|
+
if (!SocketFunction.LEGACY_INITIALIZE) {
|
|
735
|
+
let initState: InitializeState = {
|
|
736
|
+
supportsLZ4: true,
|
|
737
|
+
};
|
|
738
|
+
let initReturn: InternalReturnType = {
|
|
739
|
+
isReturn: true,
|
|
740
|
+
result: initState,
|
|
741
|
+
seqNum: INITIALIZE_STATE_SEQ_NUM,
|
|
742
|
+
};
|
|
743
|
+
let data = await SocketFunction.WIRE_SERIALIZER.serialize(initReturn);
|
|
744
|
+
await send(data);
|
|
745
|
+
}
|
|
746
|
+
|
|
758
747
|
return callFactory;
|
|
759
748
|
}
|
|
760
749
|
|
|
@@ -790,6 +779,9 @@ type CompressionStats = {
|
|
|
790
779
|
|
|
791
780
|
const compressObj = measureWrap(async function wireCallCompress(obj: unknown, stats: CompressionStats): Promise<Buffer> {
|
|
792
781
|
let buffers = await SocketFunction.WIRE_SERIALIZER.serialize(obj);
|
|
782
|
+
if (buffers.length > 1) {
|
|
783
|
+
throw new Error("Legacy CompressObj only supports single buffer");
|
|
784
|
+
}
|
|
793
785
|
let lengthBuffer = Buffer.from((new Float64Array(buffers.map(x => x.length))).buffer);
|
|
794
786
|
let buffer = Buffer.concat([lengthBuffer, ...buffers]);
|
|
795
787
|
stats.uncompressedSize += buffer.length;
|
|
@@ -811,4 +803,179 @@ const decompressObj = measureWrap(async function wireCallDecompress(obj: Buffer,
|
|
|
811
803
|
}
|
|
812
804
|
let result = await SocketFunction.WIRE_SERIALIZER.deserialize(buffers);
|
|
813
805
|
return result;
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
const compressObjLZ4 = measureWrap(async function wireCallCompressLZ4(obj: unknown, stats: CompressionStats): Promise<Buffer[]> {
|
|
809
|
+
let headerParts: number[];
|
|
810
|
+
let dataBuffers: Buffer[];
|
|
811
|
+
|
|
812
|
+
if (obj instanceof Buffer) {
|
|
813
|
+
headerParts = [1];
|
|
814
|
+
dataBuffers = [obj];
|
|
815
|
+
} else if (Array.isArray(obj) && obj.every((x: any) => x instanceof Buffer)) {
|
|
816
|
+
let bufferArray = obj as Buffer[];
|
|
817
|
+
const TARGET_SIZE = 50 * 1024 * 1024;
|
|
818
|
+
const MIN_INDIVIDUAL_SIZE = 10 * 1024 * 1024;
|
|
819
|
+
const MAX_UNSPLIT_SIZE = 100 * 1024 * 1024;
|
|
820
|
+
|
|
821
|
+
let outputBuffers: Buffer[] = [];
|
|
822
|
+
let outputDescriptors: number[][] = [];
|
|
823
|
+
let currentGroup: Buffer[] = [];
|
|
824
|
+
let currentGroupSize = 0;
|
|
825
|
+
|
|
826
|
+
function flushCurrentGroup() {
|
|
827
|
+
if (currentGroup.length > 0) {
|
|
828
|
+
outputBuffers.push(Buffer.concat(currentGroup));
|
|
829
|
+
outputDescriptors.push(currentGroup.map(b => b.length));
|
|
830
|
+
currentGroup = [];
|
|
831
|
+
currentGroupSize = 0;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
for (let buf of bufferArray) {
|
|
836
|
+
if (buf.length >= MIN_INDIVIDUAL_SIZE) {
|
|
837
|
+
flushCurrentGroup();
|
|
838
|
+
|
|
839
|
+
if (buf.length > MAX_UNSPLIT_SIZE) {
|
|
840
|
+
let offset = 0;
|
|
841
|
+
while (offset < buf.length) {
|
|
842
|
+
let chunkSize = Math.min(TARGET_SIZE, buf.length - offset);
|
|
843
|
+
outputBuffers.push(buf.slice(offset, offset + chunkSize));
|
|
844
|
+
outputDescriptors.push([chunkSize]);
|
|
845
|
+
offset += chunkSize;
|
|
846
|
+
}
|
|
847
|
+
} else {
|
|
848
|
+
outputBuffers.push(buf);
|
|
849
|
+
outputDescriptors.push([buf.length]);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
currentGroup.push(buf);
|
|
853
|
+
currentGroupSize += buf.length;
|
|
854
|
+
|
|
855
|
+
if (currentGroupSize >= TARGET_SIZE) {
|
|
856
|
+
flushCurrentGroup();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
flushCurrentGroup();
|
|
862
|
+
|
|
863
|
+
headerParts = [2, outputBuffers.length];
|
|
864
|
+
for (let descriptor of outputDescriptors) {
|
|
865
|
+
headerParts.push(descriptor.length, ...descriptor);
|
|
866
|
+
}
|
|
867
|
+
dataBuffers = outputBuffers;
|
|
868
|
+
} else {
|
|
869
|
+
let buffers = await SocketFunction.WIRE_SERIALIZER.serialize(obj);
|
|
870
|
+
headerParts = [3, buffers.length];
|
|
871
|
+
dataBuffers = buffers;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
let headerBuffer = Buffer.from((new Float64Array(headerParts)).buffer);
|
|
875
|
+
let allBuffers = [headerBuffer, ...dataBuffers];
|
|
876
|
+
|
|
877
|
+
stats.uncompressedSize += allBuffers.reduce((sum, buf) => sum + buf.length, 0);
|
|
878
|
+
|
|
879
|
+
let compressed: Buffer[] = [];
|
|
880
|
+
let startTime = Date.now();
|
|
881
|
+
let lastWarnTime = startTime;
|
|
882
|
+
let currentUncompressedSize = 0;
|
|
883
|
+
let currentCompressedSize = 0;
|
|
884
|
+
|
|
885
|
+
function logIfSlow(i: number) {
|
|
886
|
+
let now = Date.now();
|
|
887
|
+
if (now - lastWarnTime > 500) {
|
|
888
|
+
let elapsed = now - startTime;
|
|
889
|
+
console.log(`Slow LZ4 compress (${formatTime(elapsed)}: ${i + 1}/${allBuffers.length} buffers, ${formatNumber(currentUncompressedSize)}B => ${formatNumber(currentCompressedSize)}B`);
|
|
890
|
+
lastWarnTime = now;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
for (let i = 0; i < allBuffers.length; i++) {
|
|
895
|
+
let buf = allBuffers[i];
|
|
896
|
+
currentUncompressedSize += buf.length;
|
|
897
|
+
let compressedBuf = LZ4.compress(buf);
|
|
898
|
+
compressed.push(compressedBuf);
|
|
899
|
+
currentCompressedSize += compressedBuf.length;
|
|
900
|
+
|
|
901
|
+
logIfSlow(i);
|
|
902
|
+
}
|
|
903
|
+
logIfSlow(allBuffers.length);
|
|
904
|
+
|
|
905
|
+
stats.compressedSize += currentCompressedSize;
|
|
906
|
+
|
|
907
|
+
return compressed;
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
const decompressObjLZ4 = measureWrap(async function wireCallDecompressLZ4(obj: Buffer[], stats: CompressionStats): Promise<unknown> {
|
|
911
|
+
stats.compressedSize += obj.reduce((sum, buf) => sum + buf.length, 0);
|
|
912
|
+
|
|
913
|
+
let decompressed: Buffer[] = [];
|
|
914
|
+
let startTime = Date.now();
|
|
915
|
+
let lastWarnTime = startTime;
|
|
916
|
+
let currentCompressedSize = 0;
|
|
917
|
+
let currentUncompressedSize = 0;
|
|
918
|
+
function logIfSlow(i: number) {
|
|
919
|
+
let now = Date.now();
|
|
920
|
+
if (now - lastWarnTime > 500) {
|
|
921
|
+
let elapsed = now - startTime;
|
|
922
|
+
console.log(`Slow LZ4 decompress (${formatTime(elapsed)}): ${i + 1}/${obj.length} buffers, ${formatNumber(currentCompressedSize)}B => ${formatNumber(currentUncompressedSize)}B`);
|
|
923
|
+
lastWarnTime = now;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
for (let i = 0; i < obj.length; i++) {
|
|
928
|
+
let buf = obj[i];
|
|
929
|
+
currentCompressedSize += buf.length;
|
|
930
|
+
let decompressedBuf = LZ4.decompress(buf);
|
|
931
|
+
decompressed.push(decompressedBuf);
|
|
932
|
+
currentUncompressedSize += decompressedBuf.length;
|
|
933
|
+
|
|
934
|
+
logIfSlow(i);
|
|
935
|
+
}
|
|
936
|
+
logIfSlow(obj.length);
|
|
937
|
+
|
|
938
|
+
stats.uncompressedSize += currentUncompressedSize;
|
|
939
|
+
|
|
940
|
+
let headerBuffer = decompressed[0];
|
|
941
|
+
let dataBuffers = decompressed.slice(1);
|
|
942
|
+
|
|
943
|
+
let typeBuffer = headerBuffer.slice(0, 8);
|
|
944
|
+
let type = new Float64Array(typeBuffer.buffer, typeBuffer.byteOffset, 1)[0];
|
|
945
|
+
|
|
946
|
+
if (type === 1) {
|
|
947
|
+
return dataBuffers[0];
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (type === 2) {
|
|
951
|
+
let headerData = new Float64Array(headerBuffer.buffer, headerBuffer.byteOffset, headerBuffer.byteLength / 8);
|
|
952
|
+
let outputBufferCount = headerData[1];
|
|
953
|
+
|
|
954
|
+
let buffers: Buffer[] = [];
|
|
955
|
+
let headerIndex = 2;
|
|
956
|
+
|
|
957
|
+
for (let i = 0; i < outputBufferCount; i++) {
|
|
958
|
+
let inputBufferCount = headerData[headerIndex++];
|
|
959
|
+
let sizes: number[] = [];
|
|
960
|
+
for (let j = 0; j < inputBufferCount; j++) {
|
|
961
|
+
sizes.push(headerData[headerIndex++]);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
let outputBuffer = dataBuffers[i];
|
|
965
|
+
let offset = 0;
|
|
966
|
+
for (let size of sizes) {
|
|
967
|
+
buffers.push(outputBuffer.slice(offset, offset + size));
|
|
968
|
+
offset += size;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
return buffers;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if (type === 3) {
|
|
976
|
+
let result = await SocketFunction.WIRE_SERIALIZER.deserialize(dataBuffers);
|
|
977
|
+
return result;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
throw new Error(`Unknown compression type ${type}`);
|
|
814
981
|
});
|
package/src/batching.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyFunction } from "./types";
|
|
1
|
+
import { AnyFunction, MaybePromise } from "./types";
|
|
2
2
|
export type DelayType = (number | "afterio" | "immediate" | "afterpromises" | "paintLoop" | "afterPaint");
|
|
3
3
|
export declare function delay(delayTime: DelayType, immediateShortDelays?: "immediateShortDelays"): Promise<void>;
|
|
4
4
|
export declare function batchFunctionNone<Arg, Result = void>(config: unknown, fnc: (arg: Arg[]) => (Promise<Result> | Result)): (arg: Arg) => Promise<Result>;
|
|
@@ -29,3 +29,10 @@ export declare function retryFunctional<T extends AnyFunction>(fnc: T, config?:
|
|
|
29
29
|
minDelay?: number;
|
|
30
30
|
maxDelay?: number;
|
|
31
31
|
}): T;
|
|
32
|
+
export declare const safeLoop: typeof unblockLoop;
|
|
33
|
+
export declare const throttledLoop: typeof unblockLoop;
|
|
34
|
+
export declare function unblockLoop<T, R>(config: {
|
|
35
|
+
data: T[];
|
|
36
|
+
maxBlockingTime?: number;
|
|
37
|
+
backOffTime?: number;
|
|
38
|
+
} | T[], fnc: (item: T) => MaybePromise<R>): Promise<R[]>;
|
package/src/batching.ts
CHANGED
|
@@ -329,4 +329,41 @@ export function retryFunctional<T extends AnyFunction>(fnc: T, config?: {
|
|
|
329
329
|
return async function (...args: any[]) {
|
|
330
330
|
return await runFnc(args, maxRetries);
|
|
331
331
|
} as any;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export const safeLoop = unblockLoop;
|
|
335
|
+
export const throttledLoop = unblockLoop;
|
|
336
|
+
export async function unblockLoop<T, R>(config: {
|
|
337
|
+
data: T[];
|
|
338
|
+
maxBlockingTime?: number;
|
|
339
|
+
backOffTime?: number;
|
|
340
|
+
} | T[], fnc: (item: T) => MaybePromise<R>): Promise<R[]> {
|
|
341
|
+
|
|
342
|
+
let data = Array.isArray(config) ? config : config.data;
|
|
343
|
+
let maxBlockingTime = 300;
|
|
344
|
+
let backOffTime = 50;
|
|
345
|
+
if (!Array.isArray(config)) {
|
|
346
|
+
maxBlockingTime = config.maxBlockingTime ?? 300;
|
|
347
|
+
backOffTime = config.backOffTime ?? 50;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let lastYieldTime = Date.now();
|
|
351
|
+
let results: R[] = [];
|
|
352
|
+
|
|
353
|
+
for (let item of data) {
|
|
354
|
+
let result = fnc(item);
|
|
355
|
+
|
|
356
|
+
// If the function returns a promise, await it
|
|
357
|
+
if (result && typeof result === "object" && "then" in result) {
|
|
358
|
+
result = await result;
|
|
359
|
+
}
|
|
360
|
+
results.push(result);
|
|
361
|
+
|
|
362
|
+
// Check if we've been blocking for too long
|
|
363
|
+
if (Date.now() - lastYieldTime > maxBlockingTime) {
|
|
364
|
+
await delay(backOffTime);
|
|
365
|
+
lastYieldTime = Date.now();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return results;
|
|
332
369
|
}
|
package/src/callManager.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference path="../hot/HotReloadController.d.ts" />
|
|
2
2
|
import { CallerContext, CallType, ClientHookContext, FullCallType, FunctionFlags, HookContext, SocketExposedInterface, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
|
|
3
3
|
export declare function getCallFlags(call: CallType): FunctionFlags | undefined;
|
|
4
|
-
export declare function shouldCompressCall(call: CallType): boolean;
|
|
4
|
+
export declare function shouldCompressCall(call: CallType): boolean | "LZ4" | undefined;
|
|
5
5
|
export declare function performLocalCall(config: {
|
|
6
6
|
call: FullCallType;
|
|
7
7
|
caller: CallerContext;
|
package/src/callManager.ts
CHANGED
|
@@ -21,7 +21,7 @@ export function getCallFlags(call: CallType): FunctionFlags | undefined {
|
|
|
21
21
|
return classes[call.classGuid]?.shape[call.functionName];
|
|
22
22
|
}
|
|
23
23
|
export function shouldCompressCall(call: CallType) {
|
|
24
|
-
return
|
|
24
|
+
return classes[call.classGuid]?.shape[call.functionName]?.compress;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export async function performLocalCall(
|
package/src/lz4/LZ4.d.ts
ADDED
package/src/lz4/LZ4.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// NOTE: Even if we wanted to use the production version, we couldn't because it's not compatible with the client-side code, because they decided to do a file read to load in their WebAssembly.
|
|
2
|
+
import lz4_stream from "./lz4_wasm_nodejs";
|
|
3
|
+
import { measureFnc } from "../profiling/measure";
|
|
4
|
+
export class LZ4 {
|
|
5
|
+
@measureFnc
|
|
6
|
+
static compress(data: Buffer): Buffer {
|
|
7
|
+
return this.compressUntracked(data);
|
|
8
|
+
}
|
|
9
|
+
static compressUntracked(data: Buffer): Buffer {
|
|
10
|
+
try {
|
|
11
|
+
return Buffer.from(lz4_stream.compress(data));
|
|
12
|
+
} catch (e) {
|
|
13
|
+
// Rethrow non errors as properly wrapped errors
|
|
14
|
+
if (!(e && e instanceof Error)) {
|
|
15
|
+
throw new Error(`Error compressing LZ4: ${e}`);
|
|
16
|
+
}
|
|
17
|
+
throw e;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
@measureFnc
|
|
21
|
+
static decompress(data: Buffer): Buffer {
|
|
22
|
+
try {
|
|
23
|
+
return Buffer.from(lz4_stream.decompress(data));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Rethrow non errors as properly wrapped errors
|
|
26
|
+
if (!(e && e instanceof Error)) {
|
|
27
|
+
throw new Error(`Error decompressing LZ4: ${e}`);
|
|
28
|
+
}
|
|
29
|
+
throw e;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Streaming LZ4 compressor (frame format with linked blocks).
|
|
6
|
+
* Concatenate all output chunks to form a complete LZ4 frame.
|
|
7
|
+
*/
|
|
8
|
+
export class Lz4StreamCompressor {
|
|
9
|
+
free(): void;
|
|
10
|
+
[Symbol.dispose](): void;
|
|
11
|
+
compress(input: Uint8Array): Uint8Array;
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One-shot block compression with size prepended.
|
|
17
|
+
*/
|
|
18
|
+
export function compress(input: Uint8Array): Uint8Array;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* One-shot block decompression with size prepended.
|
|
22
|
+
*/
|
|
23
|
+
export function decompress(input: Uint8Array): Uint8Array;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Decompress an LZ4 stream (frame format).
|
|
27
|
+
* Auto-injects end marker if missing. On error, returns partial data and sets a warning.
|
|
28
|
+
*/
|
|
29
|
+
export function decompress_stream(input: Uint8Array): Uint8Array;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get and clear the last warning from decompression.
|
|
33
|
+
*/
|
|
34
|
+
export function get_last_warning(): string | undefined;
|