@zlink-systems/framework 0.11.0 → 0.13.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/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Configuration/EndpointNotation.d.ts +5 -4
- package/dist/contracts/Configuration/EndpointNotation.js +5 -4
- package/dist/contracts/Configuration/InternalDefaults.d.ts +0 -1
- package/dist/contracts/Configuration/InternalDefaults.js +1 -2
- package/dist/contracts/Configuration/RegistrationNormalizers.js +1 -2
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +2 -5
- package/dist/contracts/Configuration/RegistrationValidators.js +0 -1
- package/dist/contracts/Errors/ZLinkFrameworkException.d.ts +6 -7
- package/dist/contracts/Errors/ZLinkFrameworkException.js +6 -7
- package/dist/runtime/actors/actor-client.js +1 -1
- package/dist/runtime/actors/actor-mailbox.js +1 -5
- package/dist/runtime/backend/contracts/index.d.ts +2 -0
- package/dist/runtime/backend/mesh-completion-table.d.ts +1 -3
- package/dist/runtime/backend/mesh-completion-table.js +11 -15
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +11 -4
- package/dist/runtime/backend/mesh-dispatch-pump.js +201 -134
- package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +3 -4
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +51 -37
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
- package/dist/runtime/backend/node/node-backend-adapter-support.js +2 -2
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +2 -2
- package/dist/runtime/backend/node/node-event-loop-poller.d.ts +16 -0
- package/dist/runtime/backend/node/node-event-loop-poller.js +49 -0
- package/dist/runtime/backend/node/node-raw-binding-port.js +24 -41
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +8 -8
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +100 -107
- package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +3 -2
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +18 -67
- package/dist/runtime/backend/raw-binding-port.d.ts +1 -0
- package/dist/runtime/backend/runtime-values.d.ts +5 -1
- package/dist/runtime/channels/channel-dispatchers.js +2 -2
- package/dist/runtime/channels/channel-envelope.js +324 -121
- package/dist/runtime/channels/channel-multipart.d.ts +8 -1
- package/dist/runtime/channels/channel-outbound-operations.js +0 -2
- package/dist/runtime/channels/channel-receive-loops.d.ts +1 -3
- package/dist/runtime/channels/channel-receive-loops.js +25 -65
- package/dist/runtime/channels/channel-socket-registry.js +3 -1
- package/dist/runtime/channels/channel-transports.d.ts +2 -1
- package/dist/runtime/channels/channel-transports.js +12 -10
- package/dist/runtime/execution/serial-execution-queue.d.ts +5 -18
- package/dist/runtime/execution/serial-execution-queue.js +16 -80
- package/dist/runtime/foundation/event-loop-resources.d.ts +0 -3
- package/dist/runtime/foundation/event-loop-resources.js +2 -11
- package/dist/runtime/foundation/operation-registry.d.ts +2 -8
- package/dist/runtime/foundation/operation-registry.js +3 -21
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +19 -7
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +56 -10
- package/dist/runtime/foundation/service-mailbox.d.ts +1 -7
- package/dist/runtime/foundation/service-mailbox.js +3 -2
- package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -1
- package/dist/runtime/foundation/service-stateful-registry.d.ts +1 -1
- package/dist/runtime/foundation/service-stateful-registry.js +2 -2
- package/dist/runtime/foundation/service-stateful-runtime.js +14 -14
- package/dist/runtime/framework-errors-internal.d.ts +4 -4
- package/dist/runtime/framework-errors-internal.js +8 -10
- package/dist/runtime/host/actor-placement-coordinator.js +1 -1
- package/dist/runtime/host/index.js +1 -1
- package/dist/runtime/host/service-relocation-host-runtime.js +0 -4
- package/dist/runtime/host/spot-address-transport.js +1 -1
- package/dist/runtime/host/user-spot-creation-coordinator.js +1 -1
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +0 -12
- package/dist/runtime/spots/spot-node-runtime-manager.js +6 -166
- package/dist/runtime/spots/spot-serial-turn-executor.js +1 -4
- package/dist/runtime/spots/spot-timer.d.ts +3 -0
- package/dist/runtime/spots/spot-timer.js +87 -18
- package/dist/runtime/streams/session-serial-executor.js +1 -3
- package/dist/runtime/streams/stream-session-runtime.d.ts +2 -2
- package/dist/runtime/streams/stream-session-runtime.js +13 -19
- package/dist/runtime/workers/index.d.ts +0 -1
- package/dist/runtime/workers/index.js +1 -9
- package/package.json +3 -3
- package/src/contracts/Configuration/EndpointNotation.ts +5 -4
- package/src/contracts/Configuration/InternalDefaults.ts +0 -1
- package/src/contracts/Configuration/RegistrationNormalizers.ts +2 -4
- package/src/contracts/Configuration/RegistrationTypes.ts +2 -5
- package/src/contracts/Configuration/RegistrationValidators.ts +0 -1
- package/src/contracts/Errors/ZLinkFrameworkException.ts +6 -7
- package/src/runtime/actors/actor-client.ts +1 -1
- package/src/runtime/actors/actor-mailbox.ts +1 -12
- package/src/runtime/backend/contracts/index.ts +2 -0
- package/src/runtime/backend/mesh-completion-table.ts +12 -23
- package/src/runtime/backend/mesh-dispatch-pump.ts +206 -138
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +66 -35
- package/src/runtime/backend/node/node-backend-adapter-support.ts +4 -4
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +2 -2
- package/src/runtime/backend/node/node-event-loop-poller.ts +55 -0
- package/src/runtime/backend/node/node-raw-binding-port.ts +30 -45
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +97 -110
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +23 -69
- package/src/runtime/backend/raw-binding-port.ts +1 -0
- package/src/runtime/backend/runtime-values.ts +7 -1
- package/src/runtime/channels/channel-dispatchers.ts +2 -2
- package/src/runtime/channels/channel-envelope.ts +379 -109
- package/src/runtime/channels/channel-multipart.ts +9 -1
- package/src/runtime/channels/channel-outbound-operations.ts +0 -1
- package/src/runtime/channels/channel-receive-loops.ts +24 -75
- package/src/runtime/channels/channel-socket-registry.ts +4 -1
- package/src/runtime/channels/channel-transports.ts +20 -10
- package/src/runtime/execution/serial-execution-queue.ts +16 -108
- package/src/runtime/foundation/event-loop-resources.ts +2 -11
- package/src/runtime/foundation/operation-registry.ts +2 -22
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +74 -19
- package/src/runtime/foundation/service-mailbox.ts +3 -13
- package/src/runtime/foundation/service-runtime-contracts.ts +2 -1
- package/src/runtime/foundation/service-stateful-registry.ts +2 -2
- package/src/runtime/foundation/service-stateful-runtime.ts +14 -14
- package/src/runtime/framework-errors-internal.ts +8 -10
- package/src/runtime/host/actor-placement-coordinator.ts +1 -1
- package/src/runtime/host/index.ts +2 -1
- package/src/runtime/host/service-relocation-host-runtime.ts +0 -4
- package/src/runtime/host/spot-address-transport.ts +1 -1
- package/src/runtime/host/user-spot-creation-coordinator.ts +1 -1
- package/src/runtime/spots/spot-node-runtime-manager.ts +13 -233
- package/src/runtime/spots/spot-serial-turn-executor.ts +1 -8
- package/src/runtime/spots/spot-timer.ts +109 -18
- package/src/runtime/streams/session-serial-executor.ts +1 -7
- package/src/runtime/streams/stream-session-runtime.ts +13 -17
- package/src/runtime/workers/index.ts +2 -17
|
@@ -34,8 +34,6 @@ class ZLinkSpotNodeRuntimeManager {
|
|
|
34
34
|
entryActivations = new Map();
|
|
35
35
|
entryActivationStarts = new Map();
|
|
36
36
|
publishers = new Map();
|
|
37
|
-
activePublishes = new Set();
|
|
38
|
-
publishSlotWaiters = new Map();
|
|
39
37
|
disposed = false;
|
|
40
38
|
autoConnectLoops = [];
|
|
41
39
|
publishedMeshNodeDescriptors = new Map();
|
|
@@ -579,7 +577,6 @@ class ZLinkSpotNodeRuntimeManager {
|
|
|
579
577
|
this.meshPumps.clear();
|
|
580
578
|
const publishers = [...this.publishers.values()];
|
|
581
579
|
this.publishers.clear();
|
|
582
|
-
this.rejectPublishSlotWaiters(runtimeShutdownError());
|
|
583
580
|
const errors = [];
|
|
584
581
|
const settle = async (operations) => {
|
|
585
582
|
const results = await Promise.allSettled(operations);
|
|
@@ -802,35 +799,13 @@ class ZLinkSpotNodeRuntimeManager {
|
|
|
802
799
|
if (publisher === undefined) {
|
|
803
800
|
throw new configuration_1.ZLinkConfigurationException(`RouteMesh '${meshName}' publisher is not started.`);
|
|
804
801
|
}
|
|
805
|
-
|
|
806
|
-
.get(meshName)?.publisherConfig?.sendTimeoutMs ?? 1000;
|
|
807
|
-
const slot = this.acquirePublishSlot(meshName, sendTimeoutMs, signal);
|
|
808
|
-
if (slot === false) {
|
|
809
|
-
// The bounded executor-admission tokens are exhausted. Do not retain the
|
|
810
|
-
// payload, cancellation state, or a timer for this hard-overload call.
|
|
811
|
-
return Promise.resolve(this.publishTimedOut());
|
|
812
|
-
}
|
|
813
|
-
if (slot === true) {
|
|
814
|
-
return this.executePublish(publisher, meshName, channelName, topic, packetName, event, metadata);
|
|
815
|
-
}
|
|
816
|
-
return slot.then((acquired) => acquired
|
|
817
|
-
? this.executePublish(publisher, meshName, channelName, topic, packetName, event, metadata)
|
|
818
|
-
: this.publishTimedOut());
|
|
802
|
+
return this.executePublish(publisher, channelName, topic, packetName, event, metadata);
|
|
819
803
|
}
|
|
820
|
-
async executePublish(publisher,
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
try {
|
|
826
|
-
const parts = (0, flow_context_1.runWithOutboundFlow)(this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true, () => (0, channel_envelope_1.encodeChannelPublishEnvelopeParts)(channelName, topic, packetName, event, undefined, this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true, metadata));
|
|
827
|
-
const processing = publisher.publishAsync(channelName, topic, parts, undefined, undefined);
|
|
828
|
-
void Promise.resolve(processing).catch(() => undefined);
|
|
829
|
-
return Promise.resolve({ status: submission_result_1.ZLinkSubmitStatus.Submitted });
|
|
830
|
-
}
|
|
831
|
-
finally {
|
|
832
|
-
this.releasePublishSlot(meshName);
|
|
833
|
-
}
|
|
804
|
+
async executePublish(publisher, channelName, topic, packetName, event, metadata) {
|
|
805
|
+
const parts = (0, flow_context_1.runWithOutboundFlow)(this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true, () => (0, channel_envelope_1.encodeChannelPublishEnvelopeParts)(channelName, topic, packetName, event, undefined, this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true, metadata));
|
|
806
|
+
const processing = publisher.publishAsync(channelName, topic, parts, undefined, undefined);
|
|
807
|
+
void Promise.resolve(processing).catch(() => undefined);
|
|
808
|
+
return Promise.resolve({ status: submission_result_1.ZLinkSubmitStatus.Submitted });
|
|
834
809
|
}
|
|
835
810
|
tryPublish(meshName, channelName, topic, packetName, event, metadata = EMPTY_SPOT_METADATA) {
|
|
836
811
|
return this.publishWithFlags(meshName, channelName, topic, packetName, event, ZLINK_SEND_DONT_WAIT, metadata);
|
|
@@ -855,141 +830,6 @@ class ZLinkSpotNodeRuntimeManager {
|
|
|
855
830
|
throw error;
|
|
856
831
|
}
|
|
857
832
|
}
|
|
858
|
-
acquirePublishSlot(meshName, timeoutMs, signal) {
|
|
859
|
-
if (!this.activePublishes.has(meshName)) {
|
|
860
|
-
this.activePublishes.add(meshName);
|
|
861
|
-
return true;
|
|
862
|
-
}
|
|
863
|
-
const waiterCapacity = Math.max(1, this.options.registration.spotNodes
|
|
864
|
-
.get(meshName)?.publisherConfig?.sendHighWaterMark ?? 1);
|
|
865
|
-
const queue = this.publishSlotWaiters.get(meshName) ?? {
|
|
866
|
-
waiters: [],
|
|
867
|
-
head: 0,
|
|
868
|
-
count: 0
|
|
869
|
-
};
|
|
870
|
-
if (queue.count >= waiterCapacity) {
|
|
871
|
-
// This call has no bounded executor-admission token. Fail without
|
|
872
|
-
// retaining its payload in a pending work queue.
|
|
873
|
-
return false;
|
|
874
|
-
}
|
|
875
|
-
return new Promise((resolve, reject) => {
|
|
876
|
-
const waiter = {
|
|
877
|
-
resolve,
|
|
878
|
-
reject,
|
|
879
|
-
signal,
|
|
880
|
-
settled: false
|
|
881
|
-
};
|
|
882
|
-
if (timeoutMs !== -1) {
|
|
883
|
-
waiter.deadlineMs = performance.now() + Math.max(0, timeoutMs);
|
|
884
|
-
}
|
|
885
|
-
queue.waiters.push(waiter);
|
|
886
|
-
queue.count += 1;
|
|
887
|
-
this.publishSlotWaiters.set(meshName, queue);
|
|
888
|
-
if (timeoutMs !== -1) {
|
|
889
|
-
waiter.timeout = setTimeout(() => this.settlePublishSlotWaiter(meshName, waiter, false), Math.max(0, timeoutMs));
|
|
890
|
-
}
|
|
891
|
-
if (signal !== undefined) {
|
|
892
|
-
waiter.abortHandler = () => this.rejectPublishSlotWaiter(meshName, waiter, signal.reason ?? (0, abort_1.createAbortError)());
|
|
893
|
-
signal.addEventListener('abort', waiter.abortHandler, { once: true });
|
|
894
|
-
}
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
publishTimedOut() {
|
|
898
|
-
return { status: submission_result_1.ZLinkSubmitStatus.TimedOut };
|
|
899
|
-
}
|
|
900
|
-
releasePublishSlot(meshName) {
|
|
901
|
-
const queue = this.publishSlotWaiters.get(meshName);
|
|
902
|
-
while (queue !== undefined && queue.count > 0) {
|
|
903
|
-
const waiter = this.takePublishSlotWaiter(queue);
|
|
904
|
-
if (waiter === undefined)
|
|
905
|
-
continue;
|
|
906
|
-
if (waiter.settled)
|
|
907
|
-
continue;
|
|
908
|
-
if (waiter.deadlineMs !== undefined && performance.now() >= waiter.deadlineMs) {
|
|
909
|
-
this.cleanupPublishSlotWaiter(waiter);
|
|
910
|
-
waiter.settled = true;
|
|
911
|
-
waiter.resolve(false);
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
this.cleanupPublishSlotWaiter(waiter);
|
|
915
|
-
waiter.settled = true;
|
|
916
|
-
waiter.resolve(true);
|
|
917
|
-
if (queue.count === 0)
|
|
918
|
-
this.publishSlotWaiters.delete(meshName);
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
this.publishSlotWaiters.delete(meshName);
|
|
922
|
-
this.activePublishes.delete(meshName);
|
|
923
|
-
}
|
|
924
|
-
settlePublishSlotWaiter(meshName, waiter, acquired) {
|
|
925
|
-
if (waiter.settled)
|
|
926
|
-
return;
|
|
927
|
-
waiter.settled = true;
|
|
928
|
-
this.removePublishSlotWaiter(meshName, waiter);
|
|
929
|
-
this.cleanupPublishSlotWaiter(waiter);
|
|
930
|
-
waiter.resolve(acquired);
|
|
931
|
-
}
|
|
932
|
-
rejectPublishSlotWaiter(meshName, waiter, error) {
|
|
933
|
-
if (waiter.settled)
|
|
934
|
-
return;
|
|
935
|
-
waiter.settled = true;
|
|
936
|
-
this.removePublishSlotWaiter(meshName, waiter);
|
|
937
|
-
this.cleanupPublishSlotWaiter(waiter);
|
|
938
|
-
waiter.reject(error);
|
|
939
|
-
}
|
|
940
|
-
removePublishSlotWaiter(meshName, waiter) {
|
|
941
|
-
const queue = this.publishSlotWaiters.get(meshName);
|
|
942
|
-
if (queue === undefined)
|
|
943
|
-
return;
|
|
944
|
-
const index = queue.waiters.indexOf(waiter, queue.head);
|
|
945
|
-
if (index >= 0) {
|
|
946
|
-
queue.waiters[index] = undefined;
|
|
947
|
-
queue.count -= 1;
|
|
948
|
-
this.compactPublishSlotWaiters(queue);
|
|
949
|
-
}
|
|
950
|
-
if (queue.count === 0)
|
|
951
|
-
this.publishSlotWaiters.delete(meshName);
|
|
952
|
-
}
|
|
953
|
-
cleanupPublishSlotWaiter(waiter) {
|
|
954
|
-
if (waiter.timeout !== undefined)
|
|
955
|
-
clearTimeout(waiter.timeout);
|
|
956
|
-
if (waiter.abortHandler !== undefined) {
|
|
957
|
-
waiter.signal?.removeEventListener('abort', waiter.abortHandler);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
rejectPublishSlotWaiters(error) {
|
|
961
|
-
for (const [meshName, queue] of this.publishSlotWaiters) {
|
|
962
|
-
let waiter;
|
|
963
|
-
while ((waiter = this.takePublishSlotWaiter(queue)) !== undefined) {
|
|
964
|
-
this.rejectPublishSlotWaiter(meshName, waiter, error);
|
|
965
|
-
}
|
|
966
|
-
this.publishSlotWaiters.delete(meshName);
|
|
967
|
-
}
|
|
968
|
-
this.activePublishes.clear();
|
|
969
|
-
}
|
|
970
|
-
takePublishSlotWaiter(queue) {
|
|
971
|
-
while (queue.head < queue.waiters.length && queue.waiters[queue.head] === undefined) {
|
|
972
|
-
queue.head += 1;
|
|
973
|
-
}
|
|
974
|
-
const waiter = queue.waiters[queue.head];
|
|
975
|
-
if (waiter === undefined)
|
|
976
|
-
return undefined;
|
|
977
|
-
queue.waiters[queue.head] = undefined;
|
|
978
|
-
queue.head += 1;
|
|
979
|
-
queue.count -= 1;
|
|
980
|
-
this.compactPublishSlotWaiters(queue);
|
|
981
|
-
return waiter;
|
|
982
|
-
}
|
|
983
|
-
compactPublishSlotWaiters(queue) {
|
|
984
|
-
if (queue.count === 0) {
|
|
985
|
-
queue.waiters.length = 0;
|
|
986
|
-
queue.head = 0;
|
|
987
|
-
}
|
|
988
|
-
else if (queue.head >= 1024 && queue.head * 2 >= queue.waiters.length) {
|
|
989
|
-
queue.waiters.splice(0, queue.head);
|
|
990
|
-
queue.head = 0;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
833
|
}
|
|
994
834
|
exports.ZLinkSpotNodeRuntimeManager = ZLinkSpotNodeRuntimeManager;
|
|
995
835
|
function runtimeShutdownError() {
|
|
@@ -18,10 +18,7 @@ class ZLinkSpotSerialTurnExecutor {
|
|
|
18
18
|
constructor(yieldAllowed = true, sourceSpotId, schedulerOptions) {
|
|
19
19
|
this.yieldAllowed = yieldAllowed;
|
|
20
20
|
this.sourceSpotId = sourceSpotId;
|
|
21
|
-
this.scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue((record) => this.runQueuedRecord(record),
|
|
22
|
-
...schedulerOptions,
|
|
23
|
-
capacityError: schedulerOptions?.capacityError ?? (() => (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerQueueFull, 'Spot execution queue capacity was exceeded.'))
|
|
24
|
-
});
|
|
21
|
+
this.scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue((record) => this.runQueuedRecord(record), schedulerOptions);
|
|
25
22
|
}
|
|
26
23
|
get isExecuting() {
|
|
27
24
|
return this.depth > 0;
|
|
@@ -74,6 +74,8 @@ export declare class ZLinkManagedTimer implements ZLinkTimer {
|
|
|
74
74
|
private lastScheduledIndex;
|
|
75
75
|
private timeout;
|
|
76
76
|
private running;
|
|
77
|
+
private dispatchFailure;
|
|
78
|
+
private finalization;
|
|
77
79
|
constructor(name: string, periodMs: number, options: Required<ZLinkTimerOptions>, onTick: (tick: ZLinkTimerTick) => Promise<void>, onFailure?: ZLinkTimerFailureReporter | undefined, shouldWaitForRunningOnCancel?: () => boolean, clock?: ZLinkTimerClock);
|
|
78
80
|
get isDisposed(): boolean;
|
|
79
81
|
cancel(_signal?: AbortSignal): Promise<void>;
|
|
@@ -83,6 +85,7 @@ export declare class ZLinkManagedTimer implements ZLinkTimer {
|
|
|
83
85
|
private scheduleNext;
|
|
84
86
|
private fire;
|
|
85
87
|
private cancelCore;
|
|
88
|
+
private completeFinalization;
|
|
86
89
|
private prepareCaptureCore;
|
|
87
90
|
private prepareFireCore;
|
|
88
91
|
private completeFireCore;
|
|
@@ -59,7 +59,9 @@ class ZLinkSpotTimerRegistry {
|
|
|
59
59
|
: undefined;
|
|
60
60
|
if (this.executionBarrier !== undefined)
|
|
61
61
|
executionSerial?.setExecutionBarrier(this.executionBarrier);
|
|
62
|
+
let callbackStarted = false;
|
|
62
63
|
const timer = startOutsideStateLane(() => new ZLinkManagedTimer(name, periodMs, normalizeTimerOptions(options), async (tick) => {
|
|
64
|
+
callbackStarted = false;
|
|
63
65
|
const timerFlow = (0, flow_context_1.createInboundFlow)(undefined, 'Timer', this.flowCreationEnabled());
|
|
64
66
|
const operation = () => {
|
|
65
67
|
const current = this.timers.get(name);
|
|
@@ -68,6 +70,7 @@ class ZLinkSpotTimerRegistry {
|
|
|
68
70
|
}
|
|
69
71
|
if (!this.executionAllowed())
|
|
70
72
|
return undefined;
|
|
73
|
+
callbackStarted = true;
|
|
71
74
|
return (0, flow_context_1.runWithFlow)(timerFlow, () => handler.handle(spot, tick));
|
|
72
75
|
};
|
|
73
76
|
if (this.executeTimer !== undefined) {
|
|
@@ -76,9 +79,7 @@ class ZLinkSpotTimerRegistry {
|
|
|
76
79
|
else {
|
|
77
80
|
await executionSerial.execute(operation);
|
|
78
81
|
}
|
|
79
|
-
}, reportFailure, () => this.
|
|
80
|
-
? !executionSerial.isExecuting
|
|
81
|
-
: this.isTimerExecuting?.(name) !== true, this.clock));
|
|
82
|
+
}, reportFailure, () => callbackStarted, this.clock));
|
|
82
83
|
const registered = await this.lane.run(() => this.completeAddCore(name, prepared.generation, handlerType.name, timer));
|
|
83
84
|
if (!registered)
|
|
84
85
|
await timer.cancel(signal);
|
|
@@ -86,13 +87,18 @@ class ZLinkSpotTimerRegistry {
|
|
|
86
87
|
}
|
|
87
88
|
async dispose() {
|
|
88
89
|
const timers = await this.lane.run(() => {
|
|
89
|
-
const active = [...this.timers.
|
|
90
|
+
const active = [...this.timers.entries()].map(([name, entry]) => ({
|
|
91
|
+
name,
|
|
92
|
+
timer: entry.timer,
|
|
93
|
+
wait: this.executeTimer === undefined
|
|
94
|
+
? true
|
|
95
|
+
: this.isTimerExecuting?.(name) !== true
|
|
96
|
+
}));
|
|
90
97
|
this.timers.clear();
|
|
91
98
|
return active;
|
|
92
99
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
100
|
+
const finalizations = timers.map(({ timer }) => timer.dispose());
|
|
101
|
+
await Promise.all(finalizations.filter((_, index) => timers[index].wait));
|
|
96
102
|
}
|
|
97
103
|
async captureRelocation() {
|
|
98
104
|
const timers = await this.lane.run(() => [...this.timers.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -155,6 +161,7 @@ class ZLinkRegisteredTimer {
|
|
|
155
161
|
name;
|
|
156
162
|
generation;
|
|
157
163
|
timer;
|
|
164
|
+
finalization;
|
|
158
165
|
constructor(registry, name, generation, timer) {
|
|
159
166
|
this.registry = registry;
|
|
160
167
|
this.name = name;
|
|
@@ -165,7 +172,16 @@ class ZLinkRegisteredTimer {
|
|
|
165
172
|
return this.timer.isDisposed;
|
|
166
173
|
}
|
|
167
174
|
cancel(signal) {
|
|
168
|
-
|
|
175
|
+
if (this.finalization !== undefined)
|
|
176
|
+
return this.finalization;
|
|
177
|
+
try {
|
|
178
|
+
(0, abort_1.throwIfAborted)(signal);
|
|
179
|
+
}
|
|
180
|
+
catch (failure) {
|
|
181
|
+
return Promise.reject(failure);
|
|
182
|
+
}
|
|
183
|
+
this.finalization = this.registry.cancel(this.name, this.generation, this.timer);
|
|
184
|
+
return this.finalization;
|
|
169
185
|
}
|
|
170
186
|
dispose() {
|
|
171
187
|
return this.cancel();
|
|
@@ -188,6 +204,8 @@ class ZLinkManagedTimer {
|
|
|
188
204
|
lastScheduledIndex = 0n;
|
|
189
205
|
timeout;
|
|
190
206
|
running = Promise.resolve();
|
|
207
|
+
dispatchFailure;
|
|
208
|
+
finalization;
|
|
191
209
|
constructor(name, periodMs, options, onTick, onFailure, shouldWaitForRunningOnCancel = () => true, clock = systemTimerClock) {
|
|
192
210
|
this.name = name;
|
|
193
211
|
this.periodMs = periodMs;
|
|
@@ -203,9 +221,27 @@ class ZLinkManagedTimer {
|
|
|
203
221
|
get isDisposed() {
|
|
204
222
|
return this.disposed;
|
|
205
223
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
224
|
+
cancel(_signal) {
|
|
225
|
+
if (this.finalization !== undefined)
|
|
226
|
+
return this.finalization.promise;
|
|
227
|
+
let resolve;
|
|
228
|
+
let reject;
|
|
229
|
+
const promise = new Promise((complete, fail) => {
|
|
230
|
+
resolve = complete;
|
|
231
|
+
reject = fail;
|
|
232
|
+
});
|
|
233
|
+
const finalization = { promise, resolve, reject };
|
|
234
|
+
this.finalization = finalization;
|
|
235
|
+
try {
|
|
236
|
+
const preparation = this.lane.run(() => this.cancelCore());
|
|
237
|
+
void preparation.then(start => {
|
|
238
|
+
void this.completeFinalization(start, finalization).then(undefined, reject);
|
|
239
|
+
}, reject);
|
|
240
|
+
}
|
|
241
|
+
catch (failure) {
|
|
242
|
+
reject(failure);
|
|
243
|
+
}
|
|
244
|
+
return promise;
|
|
209
245
|
}
|
|
210
246
|
dispose() {
|
|
211
247
|
return this.cancel();
|
|
@@ -264,7 +300,10 @@ class ZLinkManagedTimer {
|
|
|
264
300
|
: Math.max(0, Number(this.lastScheduledIndex + 1n) * this.periodMs - this.elapsedMs());
|
|
265
301
|
this.timeout = this.clock.setTimeout(() => {
|
|
266
302
|
this.timeout = undefined;
|
|
267
|
-
|
|
303
|
+
const execution = startOutsideStateLane(() => this.fire());
|
|
304
|
+
this.running = execution.then(() => undefined, cause => this.lane.run(() => {
|
|
305
|
+
this.dispatchFailure ??= { cause };
|
|
306
|
+
}));
|
|
268
307
|
}, delayMs);
|
|
269
308
|
}
|
|
270
309
|
async fire() {
|
|
@@ -284,14 +323,44 @@ class ZLinkManagedTimer {
|
|
|
284
323
|
await this.lane.run(() => this.completeFireCore(prepared.scheduledIndex, shouldContinue));
|
|
285
324
|
}
|
|
286
325
|
cancelCore() {
|
|
287
|
-
if (this.disposed)
|
|
288
|
-
return { running: Promise.resolve() };
|
|
289
326
|
this.disposed = true;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
327
|
+
const timeout = this.timeout;
|
|
328
|
+
this.timeout = undefined;
|
|
329
|
+
return {
|
|
330
|
+
timeout,
|
|
331
|
+
running: this.shouldWaitForRunningOnCancel()
|
|
332
|
+
? this.running
|
|
333
|
+
: Promise.resolve()
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
async completeFinalization(start, finalization) {
|
|
337
|
+
const failures = [];
|
|
338
|
+
if (start.timeout !== undefined) {
|
|
339
|
+
try {
|
|
340
|
+
this.clock.clearTimeout(start.timeout);
|
|
341
|
+
}
|
|
342
|
+
catch (failure) {
|
|
343
|
+
failures.push(failure);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
await start.running;
|
|
348
|
+
}
|
|
349
|
+
catch (failure) {
|
|
350
|
+
failures.push(failure);
|
|
351
|
+
}
|
|
352
|
+
const dispatchFailure = await this.lane.run(() => this.dispatchFailure);
|
|
353
|
+
if (dispatchFailure !== undefined)
|
|
354
|
+
failures.push(dispatchFailure.cause);
|
|
355
|
+
if (failures.length === 0) {
|
|
356
|
+
finalization.resolve();
|
|
357
|
+
}
|
|
358
|
+
else if (failures.length === 1) {
|
|
359
|
+
finalization.reject(failures[0]);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
finalization.reject(new AggregateError(failures, `Timer '${this.name}' cleanup failed.`));
|
|
293
363
|
}
|
|
294
|
-
return { running: this.shouldWaitForRunningOnCancel() ? this.running : Promise.resolve() };
|
|
295
364
|
}
|
|
296
365
|
prepareCaptureCore() {
|
|
297
366
|
if (this.disposed)
|
|
@@ -5,9 +5,7 @@ const serial_execution_queue_1 = require("../execution/serial-execution-queue");
|
|
|
5
5
|
const framework_errors_internal_1 = require("../framework-errors-internal");
|
|
6
6
|
const application_job_queue_scope_1 = require("../application-jobs/application-job-queue-scope");
|
|
7
7
|
class ZLinkSessionSerialExecutor {
|
|
8
|
-
scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue(async (record) => this.execute(record), {
|
|
9
|
-
capacityError: (lane) => (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerQueueFull, `Stream Session ${lane} execution capacity is full.`, true)
|
|
10
|
-
});
|
|
8
|
+
scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue(async (record) => this.execute(record), {});
|
|
11
9
|
closed = false;
|
|
12
10
|
executeApplication(work, options = {}, onRejected) {
|
|
13
11
|
return this.submit(work, 'application', options, onRejected);
|
|
@@ -131,7 +131,7 @@ export declare class ZLinkStreamSessionNodeRuntime {
|
|
|
131
131
|
private stopped;
|
|
132
132
|
private readonly receiveAbortController;
|
|
133
133
|
private receiveLoop;
|
|
134
|
-
private readonly
|
|
134
|
+
private readonly receiveWake;
|
|
135
135
|
private receiveWorkSinceYield;
|
|
136
136
|
constructor(options: ZLinkStreamSessionNodeRuntimeOptions & ZLinkStreamLivenessOptions);
|
|
137
137
|
start(): void;
|
|
@@ -159,7 +159,7 @@ export declare class ZLinkStreamSessionNodeRuntime {
|
|
|
159
159
|
private removePendingConnectionMetadata;
|
|
160
160
|
private resolveMonitorSession;
|
|
161
161
|
private enqueueEndpointlessDisconnect;
|
|
162
|
-
private
|
|
162
|
+
private waitReceiveLoopWake;
|
|
163
163
|
private wakeReceiveLoop;
|
|
164
164
|
private getOrCreateSession;
|
|
165
165
|
}
|
|
@@ -30,31 +30,21 @@ const systemLivenessClock = {
|
|
|
30
30
|
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
31
31
|
clearTimer: (timer) => clearTimeout(timer)
|
|
32
32
|
};
|
|
33
|
-
class
|
|
34
|
-
delayMs;
|
|
33
|
+
class ZLinkStreamReceiveWake {
|
|
35
34
|
pending;
|
|
36
35
|
resolvePending;
|
|
37
|
-
timer;
|
|
38
36
|
finish = () => {
|
|
39
|
-
if (this.timer !== undefined) {
|
|
40
|
-
clearTimeout(this.timer);
|
|
41
|
-
this.timer = undefined;
|
|
42
|
-
}
|
|
43
37
|
const resolve = this.resolvePending;
|
|
44
38
|
this.pending = undefined;
|
|
45
39
|
this.resolvePending = undefined;
|
|
46
40
|
resolve?.();
|
|
47
41
|
};
|
|
48
|
-
constructor(delayMs) {
|
|
49
|
-
this.delayMs = delayMs;
|
|
50
|
-
}
|
|
51
42
|
wait() {
|
|
52
43
|
if (this.pending !== undefined)
|
|
53
44
|
return this.pending;
|
|
54
45
|
this.pending = new Promise((resolve) => {
|
|
55
46
|
this.resolvePending = resolve;
|
|
56
47
|
});
|
|
57
|
-
this.timer = setTimeout(this.finish, this.delayMs);
|
|
58
48
|
return this.pending;
|
|
59
49
|
}
|
|
60
50
|
wake() {
|
|
@@ -664,7 +654,7 @@ class ZLinkStreamSessionNodeRuntime {
|
|
|
664
654
|
stopped = false;
|
|
665
655
|
receiveAbortController = new AbortController();
|
|
666
656
|
receiveLoop;
|
|
667
|
-
|
|
657
|
+
receiveWake = new ZLinkStreamReceiveWake();
|
|
668
658
|
receiveWorkSinceYield = 0;
|
|
669
659
|
constructor(options) {
|
|
670
660
|
this.options = options;
|
|
@@ -734,14 +724,15 @@ class ZLinkStreamSessionNodeRuntime {
|
|
|
734
724
|
}
|
|
735
725
|
if (!this.options.readablePoller.wait(0)) {
|
|
736
726
|
permit.releaseAfterInternalProcessing();
|
|
737
|
-
await this.
|
|
727
|
+
await this.waitReceiveLoopWake(signal);
|
|
738
728
|
continue;
|
|
739
729
|
}
|
|
740
730
|
packet = this.takePacket();
|
|
741
731
|
if (!this.options.socket.recvPacket(packet, ZLINK_RECV_DONT_WAIT)) {
|
|
732
|
+
this.options.readablePoller.markDrained();
|
|
742
733
|
permit.releaseAfterInternalProcessing();
|
|
743
734
|
this.recyclePacket(packet);
|
|
744
|
-
await this.
|
|
735
|
+
await this.waitReceiveLoopWake(signal);
|
|
745
736
|
continue;
|
|
746
737
|
}
|
|
747
738
|
packetTransferred = true;
|
|
@@ -757,11 +748,11 @@ class ZLinkStreamSessionNodeRuntime {
|
|
|
757
748
|
if (!this.isReceiveStopped(signal) && !packetTransferred) {
|
|
758
749
|
this.options.onError?.(error);
|
|
759
750
|
this.receiveWorkSinceYield = 0;
|
|
760
|
-
await
|
|
751
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
761
752
|
}
|
|
762
753
|
else if (!this.isReceiveStopped(signal)) {
|
|
763
754
|
this.receiveWorkSinceYield = 0;
|
|
764
|
-
await
|
|
755
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
765
756
|
}
|
|
766
757
|
continue;
|
|
767
758
|
}
|
|
@@ -1091,11 +1082,14 @@ class ZLinkStreamSessionNodeRuntime {
|
|
|
1091
1082
|
pending.session.enqueueDisconnected(pending.error);
|
|
1092
1083
|
});
|
|
1093
1084
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1085
|
+
async waitReceiveLoopWake(signal) {
|
|
1086
|
+
await Promise.race([
|
|
1087
|
+
this.options.readablePoller.waitForReadable(signal),
|
|
1088
|
+
this.receiveWake.wait()
|
|
1089
|
+
]);
|
|
1096
1090
|
}
|
|
1097
1091
|
wakeReceiveLoop() {
|
|
1098
|
-
this.
|
|
1092
|
+
this.receiveWake.wake();
|
|
1099
1093
|
}
|
|
1100
1094
|
getOrCreateSession(routingId) {
|
|
1101
1095
|
const sessionId = (0, managed_stream_1.streamSessionIdFromRoutingId)(routingId);
|
|
@@ -6,7 +6,6 @@ export interface ZLinkWorkerRuntimeOptions {
|
|
|
6
6
|
readonly minThreads: number;
|
|
7
7
|
readonly maxThreads: number;
|
|
8
8
|
readonly idleTimeoutMs: number;
|
|
9
|
-
readonly maxQueueLength: number;
|
|
10
9
|
}
|
|
11
10
|
export declare function resolveWorkerRuntimeOptions(options?: ZLinkWorkerOptions): ZLinkWorkerRuntimeOptions;
|
|
12
11
|
export declare class ZLinkWorkerRuntime {
|
|
@@ -13,13 +13,11 @@ function resolveWorkerRuntimeOptions(options) {
|
|
|
13
13
|
const resolved = {
|
|
14
14
|
minThreads: options?.minThreads ?? InternalDefaults_1.DEFAULT_WORKER_MIN_THREADS,
|
|
15
15
|
maxThreads: options?.maxThreads ?? (0, InternalDefaults_1.defaultWorkerMaxThreads)(),
|
|
16
|
-
idleTimeoutMs: options?.idleTimeoutMs ?? InternalDefaults_1.DEFAULT_WORKER_IDLE_TIMEOUT_MS
|
|
17
|
-
maxQueueLength: options?.maxQueueLength ?? InternalDefaults_1.DEFAULT_WORKER_QUEUE_LENGTH
|
|
16
|
+
idleTimeoutMs: options?.idleTimeoutMs ?? InternalDefaults_1.DEFAULT_WORKER_IDLE_TIMEOUT_MS
|
|
18
17
|
};
|
|
19
18
|
requireNonNegativeInteger('Worker minThreads', resolved.minThreads);
|
|
20
19
|
requirePositiveInteger('Worker maxThreads', resolved.maxThreads);
|
|
21
20
|
requireNonNegativeInteger('Worker idleTimeoutMs', resolved.idleTimeoutMs);
|
|
22
|
-
requirePositiveInteger('Worker maxQueueLength', resolved.maxQueueLength);
|
|
23
21
|
if (resolved.maxThreads < resolved.minThreads) {
|
|
24
22
|
throw new configuration_1.ZLinkConfigurationException('Worker maxThreads must be greater than or equal to minThreads.');
|
|
25
23
|
}
|
|
@@ -53,9 +51,6 @@ class ZLinkCpuWorkerPool {
|
|
|
53
51
|
if (signal?.aborted === true) {
|
|
54
52
|
return Promise.reject((0, abort_1.createAbortError)());
|
|
55
53
|
}
|
|
56
|
-
if (this.queueCount >= this.options.maxQueueLength) {
|
|
57
|
-
return Promise.reject(workerQueueFull(this.options.maxQueueLength));
|
|
58
|
-
}
|
|
59
54
|
return new Promise((resolve, reject) => {
|
|
60
55
|
const job = {
|
|
61
56
|
source: work.toString(),
|
|
@@ -446,9 +441,6 @@ class ZLinkSerialDeliveredPromise {
|
|
|
446
441
|
return this.then((value) => this.serial.execute(() => { onfinally?.(); return value; }), (reason) => this.serial.execute(() => { onfinally?.(); throw reason; }));
|
|
447
442
|
}
|
|
448
443
|
}
|
|
449
|
-
function workerQueueFull(maxQueueLength) {
|
|
450
|
-
return (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerQueueFull, `CPU worker queue is full (maxQueueLength=${maxQueueLength}).`, true);
|
|
451
|
-
}
|
|
452
444
|
function workerTimedOut(timeoutMs) {
|
|
453
445
|
return (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerTimedOut, `Worker job timed out after ${timeoutMs}ms.`, true);
|
|
454
446
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zlink-systems/framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@opentelemetry/api": "^1.9.1",
|
|
21
21
|
"@opentelemetry/api-logs": "^0.221.0",
|
|
22
|
-
"@zlink-systems/stream-wire": "0.
|
|
23
|
-
"@zlink-systems/zlink": "
|
|
22
|
+
"@zlink-systems/stream-wire": "0.13.0",
|
|
23
|
+
"@zlink-systems/zlink": "1.1.0"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deterministic endpoint-notation normalization (endpoint 표기
|
|
3
|
-
*
|
|
2
|
+
* Deterministic endpoint-notation normalization (endpoint 표기 정책),
|
|
3
|
+
* applied identically across all four language bindings so endpoint
|
|
4
|
+
* strings compare equal.
|
|
4
5
|
*
|
|
5
6
|
* `new URL()` cannot represent this domain: it throws on bracketed IPv6
|
|
6
7
|
* literals that carry a zone id (`tcp://[fe80::1%eth0]:80`, even with the
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
* public connect/disconnect APIs). Everything downstream compares the
|
|
17
18
|
* resulting strings with plain `===`.
|
|
18
19
|
*
|
|
19
|
-
* Rules
|
|
20
|
+
* Rules, applied only to authority-bearing schemes
|
|
20
21
|
* (`tcp`, `tls`, `ws`, `wss` -- schemes whose grammar after `scheme://` is
|
|
21
22
|
* `host:port`):
|
|
22
23
|
* - scheme: lowercased
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
* `normalizeEndpoint(normalizeEndpoint(x)) === normalizeEndpoint(x)`.
|
|
40
41
|
*
|
|
41
42
|
* DNS names are never resolved. `localhost` and `127.0.0.1` remain distinct
|
|
42
|
-
* endpoints
|
|
43
|
+
* endpoints.
|
|
43
44
|
*/
|
|
44
45
|
|
|
45
46
|
const SCHEME_PATTERN = /^([A-Za-z][A-Za-z0-9+.-]*):\/\/(.*)$/s;
|
|
@@ -6,7 +6,6 @@ export const DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE = 64 * 1024;
|
|
|
6
6
|
/** Defaults shared by registration normalization and the runtime worker pool. */
|
|
7
7
|
export const DEFAULT_WORKER_MIN_THREADS = 0;
|
|
8
8
|
export const DEFAULT_WORKER_IDLE_TIMEOUT_MS = 30_000;
|
|
9
|
-
export const DEFAULT_WORKER_QUEUE_LENGTH = 1024;
|
|
10
9
|
|
|
11
10
|
export function defaultWorkerMaxThreads(): number {
|
|
12
11
|
return Math.max(2, os.availableParallelism());
|
|
@@ -25,8 +25,7 @@ import {
|
|
|
25
25
|
defaultWorkerMaxThreads,
|
|
26
26
|
DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE,
|
|
27
27
|
DEFAULT_WORKER_IDLE_TIMEOUT_MS,
|
|
28
|
-
DEFAULT_WORKER_MIN_THREADS
|
|
29
|
-
DEFAULT_WORKER_QUEUE_LENGTH
|
|
28
|
+
DEFAULT_WORKER_MIN_THREADS
|
|
30
29
|
} from './InternalDefaults';
|
|
31
30
|
|
|
32
31
|
export function normalizeWorkerOptions(
|
|
@@ -37,8 +36,7 @@ export function normalizeWorkerOptions(
|
|
|
37
36
|
return {
|
|
38
37
|
minThreads: partial.minThreads ?? DEFAULT_WORKER_MIN_THREADS,
|
|
39
38
|
maxThreads: partial.maxThreads ?? defaultWorkerMaxThreads(),
|
|
40
|
-
idleTimeoutMs: partial.idleTimeoutMs ?? DEFAULT_WORKER_IDLE_TIMEOUT_MS
|
|
41
|
-
maxQueueLength: partial.maxQueueLength ?? DEFAULT_WORKER_QUEUE_LENGTH
|
|
39
|
+
idleTimeoutMs: partial.idleTimeoutMs ?? DEFAULT_WORKER_IDLE_TIMEOUT_MS
|
|
42
40
|
};
|
|
43
41
|
}
|
|
44
42
|
|