@push.rocks/smartrust 2.5.1 → 2.6.1
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/.smartconfig.json +4 -2
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.rustbinarylocator.d.ts +14 -3
- package/dist_ts/classes.rustbinarylocator.js +56 -16
- package/dist_ts/classes.rustbinarylocatorerror.d.ts +15 -3
- package/dist_ts/classes.rustbinarylocatorerror.js +9 -4
- package/dist_ts/classes.rustbridge.d.ts +2 -1
- package/dist_ts/classes.rustbridge.js +69 -40
- package/dist_ts/classes.sockettransport.js +1 -1
- package/dist_ts/classes.stdiotransport.js +1 -1
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/index.js +1 -1
- package/dist_ts/paths.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +9 -9
- package/readme.md +34 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.rustbinarylocator.ts +61 -15
- package/ts/classes.rustbinarylocatorerror.ts +19 -2
- package/ts/classes.rustbridge.ts +76 -40
- package/ts/index.ts +1 -1
- package/ts/plugins.ts +2 -1
package/ts/classes.rustbridge.ts
CHANGED
|
@@ -31,10 +31,44 @@ const defaultLogger: IRustBridgeLogger = {
|
|
|
31
31
|
log() {},
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/** A bridge deadline armed by `armDeadline`. */
|
|
35
|
+
interface IBridgeDeadline {
|
|
36
|
+
/** Cancel the deadline, including an expiry that already fired and still waits for its turn. */
|
|
37
|
+
clear(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Arm a deadline that never beats an answer already waiting to be read.
|
|
42
|
+
*
|
|
43
|
+
* When the process resumes from a stall (a frozen cgroup, a stopped or descheduled VM, a long
|
|
44
|
+
* synchronous task), Node runs every expired timer before it polls for I/O again, so a deadline
|
|
45
|
+
* can expire while the answer it bounds sits unread in the pipe: a frozen Pallet guest timed out a
|
|
46
|
+
* command whose answer its Rust side had written 13.5 ms before the freeze. The expiry therefore
|
|
47
|
+
* acts one event-loop turn later: `setImmediate` runs after the next poll phase, which reads
|
|
48
|
+
* whatever had arrived. Whatever that read settles clears the deadline, and `clear()` cancels the
|
|
49
|
+
* waiting expiry too, so an answer that won is never abandoned afterwards. Every bridge deadline
|
|
50
|
+
* is armed here, never with a bare `setTimeout`.
|
|
51
|
+
*/
|
|
52
|
+
const armDeadline = (timeoutMs: number, onExpired: () => void): IBridgeDeadline => {
|
|
53
|
+
let expiry: ReturnType<typeof setImmediate> | null = null;
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
expiry = setImmediate(onExpired);
|
|
56
|
+
}, timeoutMs);
|
|
57
|
+
return {
|
|
58
|
+
clear: () => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
if (expiry) {
|
|
61
|
+
clearImmediate(expiry);
|
|
62
|
+
expiry = null;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
34
68
|
interface IPendingRequest {
|
|
35
69
|
resolve: (value: any) => void;
|
|
36
70
|
reject: (error: Error) => void;
|
|
37
|
-
|
|
71
|
+
deadline: IBridgeDeadline | null;
|
|
38
72
|
method: string;
|
|
39
73
|
abortSignal?: AbortSignal;
|
|
40
74
|
abortHandler?: () => void;
|
|
@@ -155,7 +189,8 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
155
189
|
/**
|
|
156
190
|
* Spawn the Rust binary and wait for it to signal readiness.
|
|
157
191
|
* Returns true if the binary was found and spawned successfully.
|
|
158
|
-
* Rejects with RustBinaryLocatorError for an invalid explicit binaryPath
|
|
192
|
+
* Rejects with RustBinaryLocatorError for an invalid explicit binaryPath and,
|
|
193
|
+
* inside a single executable application, for an embedded sidecar it cannot provide.
|
|
159
194
|
*/
|
|
160
195
|
public spawn(): Promise<boolean> {
|
|
161
196
|
return this.activate(async (generationArg) => {
|
|
@@ -289,18 +324,18 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
289
324
|
|
|
290
325
|
return new Promise<boolean>((resolve) => {
|
|
291
326
|
let settled = false;
|
|
292
|
-
let
|
|
293
|
-
let
|
|
327
|
+
let readyDeadline: IBridgeDeadline | null = null;
|
|
328
|
+
let reconnectReadyDeadline: IBridgeDeadline | null = null;
|
|
294
329
|
|
|
295
330
|
const isCurrent = (): boolean => this.isCurrentTransport(transportArg, generation);
|
|
296
331
|
const settle = (valueArg: boolean): void => {
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
|
|
332
|
+
if (readyDeadline) {
|
|
333
|
+
readyDeadline.clear();
|
|
334
|
+
readyDeadline = null;
|
|
300
335
|
}
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
|
|
336
|
+
if (reconnectReadyDeadline) {
|
|
337
|
+
reconnectReadyDeadline.clear();
|
|
338
|
+
reconnectReadyDeadline = null;
|
|
304
339
|
}
|
|
305
340
|
if (settled) {
|
|
306
341
|
return;
|
|
@@ -335,9 +370,9 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
335
370
|
if (!isCurrent() || this.lifecycleState !== 'starting') {
|
|
336
371
|
return;
|
|
337
372
|
}
|
|
338
|
-
if (
|
|
339
|
-
|
|
340
|
-
|
|
373
|
+
if (reconnectReadyDeadline) {
|
|
374
|
+
reconnectReadyDeadline.clear();
|
|
375
|
+
reconnectReadyDeadline = null;
|
|
341
376
|
}
|
|
342
377
|
this.remoteCancelSupported = this.readCancelCapability(readyEvent);
|
|
343
378
|
this.lifecycleState = 'ready';
|
|
@@ -373,9 +408,9 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
373
408
|
this.rejectAllPendingRequests(`${this.options.binaryName} process exited`);
|
|
374
409
|
this.settleAllRemoteCancellations();
|
|
375
410
|
if (!intentional && transportArg.reconnecting === true) {
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
411
|
+
if (reconnectReadyDeadline) {
|
|
412
|
+
reconnectReadyDeadline.clear();
|
|
413
|
+
reconnectReadyDeadline = null;
|
|
379
414
|
}
|
|
380
415
|
this.lifecycleState = 'starting';
|
|
381
416
|
// Idle capability owners must observe loss before any reconnect timer
|
|
@@ -411,10 +446,10 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
411
446
|
this.logger.log('info', 'Transport reconnected, waiting for ready event');
|
|
412
447
|
this.lifecycleState = 'starting';
|
|
413
448
|
this.remoteCancelSupported = false;
|
|
414
|
-
if (
|
|
415
|
-
|
|
449
|
+
if (reconnectReadyDeadline) {
|
|
450
|
+
reconnectReadyDeadline.clear();
|
|
416
451
|
}
|
|
417
|
-
|
|
452
|
+
reconnectReadyDeadline = armDeadline(this.options.readyTimeoutMs, () => {
|
|
418
453
|
if (!isCurrent() || this.lifecycleState !== 'starting') {
|
|
419
454
|
return;
|
|
420
455
|
}
|
|
@@ -423,7 +458,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
423
458
|
`Reconnected process did not send ready event within ${this.options.readyTimeoutMs}ms`,
|
|
424
459
|
);
|
|
425
460
|
requestConfirmedCleanup();
|
|
426
|
-
}
|
|
461
|
+
});
|
|
427
462
|
this.emit('reconnected');
|
|
428
463
|
});
|
|
429
464
|
|
|
@@ -431,9 +466,9 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
431
466
|
if (!isCurrent()) {
|
|
432
467
|
return;
|
|
433
468
|
}
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
|
|
469
|
+
if (reconnectReadyDeadline) {
|
|
470
|
+
reconnectReadyDeadline.clear();
|
|
471
|
+
reconnectReadyDeadline = null;
|
|
437
472
|
}
|
|
438
473
|
this.isRunning = false;
|
|
439
474
|
this.transport = null;
|
|
@@ -443,7 +478,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
443
478
|
settle(false);
|
|
444
479
|
});
|
|
445
480
|
|
|
446
|
-
|
|
481
|
+
readyDeadline = armDeadline(this.options.readyTimeoutMs, () => {
|
|
447
482
|
if (!isCurrent() || this.lifecycleState !== 'starting') {
|
|
448
483
|
return;
|
|
449
484
|
}
|
|
@@ -452,7 +487,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
452
487
|
`Process did not send ready event within ${this.options.readyTimeoutMs}ms`,
|
|
453
488
|
);
|
|
454
489
|
requestConfirmedCleanup();
|
|
455
|
-
}
|
|
490
|
+
});
|
|
456
491
|
|
|
457
492
|
let connectionPromise: Promise<void>;
|
|
458
493
|
try {
|
|
@@ -543,7 +578,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
543
578
|
const pending: IPendingRequest = {
|
|
544
579
|
resolve,
|
|
545
580
|
reject,
|
|
546
|
-
|
|
581
|
+
deadline: null,
|
|
547
582
|
method,
|
|
548
583
|
abortSignal: options?.signal,
|
|
549
584
|
admissionReservation,
|
|
@@ -569,13 +604,13 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
569
604
|
}
|
|
570
605
|
}
|
|
571
606
|
|
|
572
|
-
pending.
|
|
607
|
+
pending.deadline = armDeadline(timeoutMs, () => {
|
|
573
608
|
this.abandonPendingRequest(
|
|
574
609
|
id,
|
|
575
610
|
'ERR_RUST_BRIDGE_REQUEST_TIMEOUT',
|
|
576
611
|
`Command '${method}' timed out after ${timeoutMs}ms`,
|
|
577
612
|
);
|
|
578
|
-
}
|
|
613
|
+
});
|
|
579
614
|
|
|
580
615
|
const currentAvailabilityError = this.getRequestAvailabilityError(
|
|
581
616
|
method,
|
|
@@ -743,7 +778,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
743
778
|
const pending: IPendingRequest = {
|
|
744
779
|
resolve,
|
|
745
780
|
reject,
|
|
746
|
-
|
|
781
|
+
deadline: null,
|
|
747
782
|
method,
|
|
748
783
|
abortSignal: options?.signal,
|
|
749
784
|
admissionReservation,
|
|
@@ -764,14 +799,14 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
764
799
|
};
|
|
765
800
|
options.signal.addEventListener('abort', pending.abortHandler, { once: true });
|
|
766
801
|
}
|
|
767
|
-
pending.
|
|
802
|
+
pending.deadline = armDeadline(timeoutMs, () => {
|
|
768
803
|
this.settlePendingRequest(id, {
|
|
769
804
|
outcome: 'reject',
|
|
770
805
|
error: new RustBridgeRequestError(`Command '${method}' timed out after ${timeoutMs}ms`, {
|
|
771
806
|
code: 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT', requestId: id, method,
|
|
772
807
|
}),
|
|
773
808
|
});
|
|
774
|
-
}
|
|
809
|
+
});
|
|
775
810
|
|
|
776
811
|
void (async (): Promise<void> => {
|
|
777
812
|
try {
|
|
@@ -887,7 +922,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
887
922
|
const pending: IPendingRequest = {
|
|
888
923
|
resolve: (result: any) => streaming.finish(result),
|
|
889
924
|
reject: (error: Error) => streaming.fail(error),
|
|
890
|
-
|
|
925
|
+
deadline: null,
|
|
891
926
|
method,
|
|
892
927
|
abortSignal: signal,
|
|
893
928
|
streaming,
|
|
@@ -1151,16 +1186,16 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
1151
1186
|
|
|
1152
1187
|
/** Start or restart the inactivity deadline of one streaming request. */
|
|
1153
1188
|
private armStreamingTimeout(id: string, pending: IPendingRequest, afterChunkArg: boolean): void {
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1189
|
+
// Clearing also cancels an expiry that fired and waits for its turn: a chunk read in that turn
|
|
1190
|
+
// keeps the stream alive.
|
|
1191
|
+
pending.deadline?.clear();
|
|
1157
1192
|
const timeoutMs = this.options.streamTimeoutMs ?? this.options.requestTimeoutMs;
|
|
1158
1193
|
const message = afterChunkArg
|
|
1159
1194
|
? `Streaming command '${pending.method}' timed out after ${timeoutMs}ms of inactivity`
|
|
1160
1195
|
: `Streaming command '${pending.method}' timed out after ${timeoutMs}ms`;
|
|
1161
|
-
pending.
|
|
1196
|
+
pending.deadline = armDeadline(timeoutMs, () => {
|
|
1162
1197
|
this.abandonPendingRequest(id, 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT', message);
|
|
1163
|
-
}
|
|
1198
|
+
});
|
|
1164
1199
|
}
|
|
1165
1200
|
|
|
1166
1201
|
/**
|
|
@@ -1338,9 +1373,10 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
1338
1373
|
|
|
1339
1374
|
this.pendingRequests.delete(id);
|
|
1340
1375
|
this.releasePendingRequestReservation(pending.admissionReservation);
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
pending.
|
|
1376
|
+
// A settled request's deadline does nothing more, even when it fired and waits for its turn.
|
|
1377
|
+
if (pending.deadline) {
|
|
1378
|
+
pending.deadline.clear();
|
|
1379
|
+
pending.deadline = null;
|
|
1344
1380
|
}
|
|
1345
1381
|
if (pending.abortSignal && pending.abortHandler) {
|
|
1346
1382
|
pending.abortSignal.removeEventListener('abort', pending.abortHandler);
|
package/ts/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export {
|
|
|
7
7
|
type TRustBridgeRequestErrorCode,
|
|
8
8
|
} from './classes.rustbridgerequesterror.js';
|
|
9
9
|
export { RustBinaryLocator } from './classes.rustbinarylocator.js';
|
|
10
|
-
export { RustBinaryLocatorError } from './classes.rustbinarylocatorerror.js';
|
|
10
|
+
export { RustBinaryLocatorError, type TRustBinaryLocatorErrorCode } from './classes.rustbinarylocatorerror.js';
|
|
11
11
|
export { StreamingResponse } from './classes.streamingresponse.js';
|
|
12
12
|
export { StdioTransport } from './classes.stdiotransport.js';
|
|
13
13
|
export { SocketTransport } from './classes.sockettransport.js';
|
package/ts/plugins.ts
CHANGED