@zlink-systems/framework 0.30.0 → 0.30.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/LICENSE +150 -68
- package/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Actors/ZLinkActorClient.d.ts +2 -1
- package/dist/contracts/Channels/Calls.d.ts +2 -1
- package/dist/contracts/Common/ZLinkMessageMetadata.js +11 -5
- package/dist/contracts/Handlers/Attributes.d.ts +5 -13
- package/dist/contracts/Handlers/Attributes.js +7 -11
- package/dist/contracts/Handlers/JsonContract.d.ts +2 -46
- package/dist/contracts/Handlers/JsonContract.js +4 -153
- package/dist/contracts/Spots/Contracts.d.ts +2 -1
- package/dist/nest-integration.d.ts +1 -1
- package/dist/nest-integration.js +2 -1
- package/dist/runtime/actors/actor-client.d.ts +1 -1
- package/dist/runtime/actors/actor-client.js +14 -12
- package/dist/runtime/actors/actor-handoff.d.ts +1 -1
- package/dist/runtime/actors/actor-handoff.js +7 -4
- package/dist/runtime/actors/actor-join-relocation.d.ts +1 -0
- package/dist/runtime/actors/actor-local-native-join.js +9 -12
- package/dist/runtime/actors/actor-remote-wire.js +15 -0
- package/dist/runtime/actors/actor-routed-join-transport.d.ts +6 -2
- package/dist/runtime/actors/actor-routed-json-request.d.ts +2 -0
- package/dist/runtime/actors/index.d.ts +2 -1
- package/dist/runtime/actors/index.js +18 -6
- package/dist/runtime/actors/spot-actor-dispatch.js +2 -2
- package/dist/runtime/admission.d.ts +1 -1
- package/dist/runtime/admission.js +15 -5
- package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +6 -8
- package/dist/runtime/application-jobs/application-ingress-record-owner.js +39 -22
- package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +2 -1
- package/dist/runtime/application-jobs/application-job-queue-scope.js +37 -22
- package/dist/runtime/application-jobs/contracts.d.ts +10 -1
- package/dist/runtime/backend/contracts/index.d.ts +18 -9
- package/dist/runtime/backend/mesh-completion-table.d.ts +9 -1
- package/dist/runtime/backend/mesh-completion-table.js +149 -49
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +3 -0
- package/dist/runtime/backend/mesh-dispatch-pump.js +32 -11
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
- package/dist/runtime/backend/node/node-backend-adapter-support.js +16 -9
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +5 -2
- package/dist/runtime/backend/node/node-raw-binding-port.js +42 -22
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +13 -5
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +69 -51
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +6 -6
- package/dist/runtime/backend/raw-binding-port.d.ts +2 -2
- package/dist/runtime/backend/runtime-message.d.ts +1 -0
- package/dist/runtime/backend/runtime-message.js +5 -0
- package/dist/runtime/backend/runtime-values.d.ts +1 -0
- package/dist/runtime/backend/runtime-values.js +2 -1
- package/dist/runtime/channels/channel-clients.d.ts +1 -0
- package/dist/runtime/channels/channel-clients.js +58 -21
- package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +3 -2
- package/dist/runtime/channels/channel-dispatch-pipeline.js +20 -11
- package/dist/runtime/channels/channel-dispatch-services.d.ts +1 -0
- package/dist/runtime/channels/channel-dispatch-services.js +45 -0
- package/dist/runtime/channels/channel-dispatchers.d.ts +1 -1
- package/dist/runtime/channels/channel-dispatchers.js +27 -26
- package/dist/runtime/channels/channel-envelope-inspection.d.ts +1 -2
- package/dist/runtime/channels/channel-envelope-inspection.js +1 -16
- package/dist/runtime/channels/channel-envelope.d.ts +1 -0
- package/dist/runtime/channels/channel-envelope.js +83 -8
- package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
- package/dist/runtime/channels/channel-outbound-operations.js +8 -8
- package/dist/runtime/channels/channel-runtime-manager.d.ts +6 -6
- package/dist/runtime/channels/channel-runtime-manager.js +12 -12
- package/dist/runtime/channels/channel-transports.d.ts +18 -15
- package/dist/runtime/channels/channel-transports.js +48 -31
- package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
- package/dist/runtime/channels/local-spot-route-transport.js +2 -1
- package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +2 -2
- package/dist/runtime/channels/router-socket-spot-route-transport.js +4 -4
- package/dist/runtime/channels/source-spot-router.d.ts +2 -2
- package/dist/runtime/channels/source-spot-router.js +4 -4
- package/dist/runtime/channels/spot-node-route-transport.d.ts +2 -2
- package/dist/runtime/channels/spot-node-route-transport.js +4 -4
- package/dist/runtime/channels/spot-route-bridge-transport.d.ts +2 -2
- package/dist/runtime/channels/spot-route-bridge-transport.js +4 -4
- package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +5 -4
- package/dist/runtime/channels/spot-route-dispatch-strategy.js +18 -18
- package/dist/runtime/execution/serial-execution-queue.d.ts +1 -0
- package/dist/runtime/execution/serial-execution-queue.js +1 -0
- package/dist/runtime/foundation/actor-join-recovery-codec.js +2 -2
- package/dist/runtime/foundation/durable-service-request.d.ts +14 -0
- package/dist/runtime/foundation/durable-service-request.js +98 -0
- package/dist/runtime/foundation/operation-identity.d.ts +1 -0
- package/dist/runtime/foundation/operation-identity.js +5 -1
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +22 -5
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +356 -232
- package/dist/runtime/foundation/service-relocation-object-owner.d.ts +1 -0
- package/dist/runtime/foundation/service-relocation-object-owner.js +1 -0
- package/dist/runtime/foundation/service-relocation-runtime.d.ts +2 -0
- package/dist/runtime/foundation/service-relocation-runtime.js +13 -1
- package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -0
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +4 -4
- package/dist/runtime/foundation/service-stateful-runtime.js +44 -103
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -5
- package/dist/runtime/foundation/service-topology-registry.d.ts +12 -2
- package/dist/runtime/foundation/service-topology-registry.js +61 -27
- package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +3 -3
- package/dist/runtime/foundation/service-wire-m6a-codec.js +36 -38
- package/dist/runtime/handlers/handler-instance-scope.d.ts +8 -0
- package/dist/runtime/handlers/handler-instance-scope.js +54 -11
- package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
- package/dist/runtime/host/application-job-queue.d.ts +19 -7
- package/dist/runtime/host/application-job-queue.js +150 -42
- package/dist/runtime/host/entry-actor-runtime.d.ts +1 -0
- package/dist/runtime/host/entry-actor-runtime.js +3 -0
- package/dist/runtime/host/index.d.ts +7 -1
- package/dist/runtime/host/index.js +142 -118
- package/dist/runtime/host/remote-bound-session-relay.js +2 -18
- package/dist/runtime/host/route-mesh-runtime.js +1 -1
- package/dist/runtime/host/service-relocation-control.d.ts +1 -0
- package/dist/runtime/host/service-relocation-control.js +10 -4
- package/dist/runtime/host/service-relocation-host-runtime.d.ts +6 -8
- package/dist/runtime/host/service-relocation-host-runtime.js +213 -241
- package/dist/runtime/host/spot-address-transport.js +3 -2
- package/dist/runtime/host/spot-runtime-options-factory.js +1 -0
- package/dist/runtime/locations/actor-location-claims.d.ts +2 -2
- package/dist/runtime/locations/actor-location-claims.js +34 -19
- package/dist/runtime/locations/lifecycle.d.ts +1 -2
- package/dist/runtime/locations/lifecycle.js +2 -5
- package/dist/runtime/locations/runtime.d.ts +1 -1
- package/dist/runtime/locations/runtime.js +24 -23
- package/dist/runtime/messaging/framework-json-v1.d.ts +4 -6
- package/dist/runtime/messaging/framework-json-v1.js +38 -294
- package/dist/runtime/messaging/packet-name.d.ts +1 -1
- package/dist/runtime/messaging/packet-name.js +8 -24
- package/dist/runtime/messaging/request-result.d.ts +2 -0
- package/dist/runtime/messaging/request-result.js +54 -0
- package/dist/runtime/messaging/submission-result.d.ts +1 -0
- package/dist/runtime/messaging/submission-result.js +5 -2
- package/dist/runtime/protocol/service_wire_codec.generated.d.ts +15 -0
- package/dist/runtime/protocol/service_wire_codec.generated.js +144 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +9 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +14 -1
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +7 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +33 -19
- package/dist/runtime/spots/index.d.ts +3 -0
- package/dist/runtime/spots/index.js +9 -2
- package/dist/runtime/spots/spot-activation.d.ts +1 -0
- package/dist/runtime/spots/spot-activation.js +17 -8
- package/dist/runtime/spots/spot-entry-activation.js +6 -2
- package/dist/runtime/spots/spot-handler-registry.d.ts +3 -1
- package/dist/runtime/spots/spot-handler-registry.js +18 -8
- package/dist/runtime/spots/spot-node-autoconnect.js +4 -3
- package/dist/runtime/spots/spot-outbound.d.ts +3 -0
- package/dist/runtime/spots/spot-outbound.js +24 -15
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +1 -0
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +1 -1
- package/dist/runtime/spots/spot-runtime-ports.d.ts +1 -0
- package/dist/runtime/spots/spot-subscription-dispatch.js +0 -3
- package/dist/runtime/spots/spot-timer.js +1 -0
- package/dist/runtime/streams/stream-session-runtime.d.ts +1 -1
- package/dist/runtime/streams/stream-session-runtime.js +13 -27
- package/package.json +2 -2
- package/src/contracts/Actors/ZLinkActorClient.ts +2 -1
- package/src/contracts/Channels/Calls.ts +2 -1
- package/src/contracts/Common/ZLinkMessageMetadata.ts +13 -8
- package/src/contracts/Handlers/Attributes.ts +14 -21
- package/src/contracts/Handlers/JsonContract.ts +8 -216
- package/src/contracts/Spots/Contracts.ts +2 -1
- package/src/nest-integration.ts +1 -0
- package/src/runtime/actors/actor-client.ts +40 -16
- package/src/runtime/actors/actor-handoff.ts +9 -4
- package/src/runtime/actors/actor-join-relocation.ts +1 -0
- package/src/runtime/actors/actor-local-native-join.ts +17 -26
- package/src/runtime/actors/actor-remote-wire.ts +17 -0
- package/src/runtime/actors/actor-routed-join-transport.ts +14 -4
- package/src/runtime/actors/actor-routed-json-request.ts +10 -2
- package/src/runtime/actors/index.ts +19 -5
- package/src/runtime/actors/spot-actor-dispatch.ts +4 -2
- package/src/runtime/admission.ts +18 -5
- package/src/runtime/application-jobs/application-ingress-record-owner.ts +41 -25
- package/src/runtime/application-jobs/application-job-queue-scope.ts +51 -22
- package/src/runtime/application-jobs/contracts.ts +14 -1
- package/src/runtime/backend/contracts/index.ts +53 -14
- package/src/runtime/backend/mesh-completion-table.ts +156 -57
- package/src/runtime/backend/mesh-dispatch-pump.ts +40 -10
- package/src/runtime/backend/node/node-backend-adapter-support.ts +17 -8
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +12 -2
- package/src/runtime/backend/node/node-raw-binding-port.ts +57 -30
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +128 -65
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +21 -6
- package/src/runtime/backend/raw-binding-port.ts +7 -2
- package/src/runtime/backend/runtime-message.ts +6 -0
- package/src/runtime/backend/runtime-values.ts +1 -0
- package/src/runtime/channels/channel-clients.ts +90 -41
- package/src/runtime/channels/channel-dispatch-pipeline.ts +69 -34
- package/src/runtime/channels/channel-dispatch-services.ts +69 -1
- package/src/runtime/channels/channel-dispatchers.ts +29 -27
- package/src/runtime/channels/channel-envelope-inspection.ts +2 -16
- package/src/runtime/channels/channel-envelope.ts +133 -8
- package/src/runtime/channels/channel-outbound-operations.ts +22 -8
- package/src/runtime/channels/channel-runtime-manager.ts +29 -12
- package/src/runtime/channels/channel-transports.ts +128 -60
- package/src/runtime/channels/local-spot-route-transport.ts +3 -1
- package/src/runtime/channels/router-socket-spot-route-transport.ts +13 -4
- package/src/runtime/channels/source-spot-router.ts +8 -4
- package/src/runtime/channels/spot-node-route-transport.ts +8 -4
- package/src/runtime/channels/spot-route-bridge-transport.ts +6 -4
- package/src/runtime/channels/spot-route-dispatch-strategy.ts +47 -18
- package/src/runtime/execution/serial-execution-queue.ts +2 -0
- package/src/runtime/foundation/actor-join-recovery-codec.ts +2 -2
- package/src/runtime/foundation/durable-service-request.ts +137 -0
- package/src/runtime/foundation/operation-identity.ts +5 -1
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +484 -293
- package/src/runtime/foundation/service-relocation-object-owner.ts +2 -0
- package/src/runtime/foundation/service-relocation-runtime.ts +18 -1
- package/src/runtime/foundation/service-runtime-contracts.ts +7 -1
- package/src/runtime/foundation/service-stateful-runtime.ts +67 -125
- package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -5
- package/src/runtime/foundation/service-topology-registry.ts +91 -36
- package/src/runtime/foundation/service-wire-m6a-codec.ts +57 -38
- package/src/runtime/handlers/handler-instance-scope.ts +67 -16
- package/src/runtime/host/actor-runtime-options-factory.ts +13 -2
- package/src/runtime/host/application-job-queue.ts +175 -41
- package/src/runtime/host/entry-actor-runtime.ts +4 -0
- package/src/runtime/host/index.ts +201 -156
- package/src/runtime/host/remote-bound-session-relay.ts +2 -23
- package/src/runtime/host/route-mesh-runtime.ts +1 -1
- package/src/runtime/host/service-relocation-control.ts +11 -5
- package/src/runtime/host/service-relocation-host-runtime.ts +315 -291
- package/src/runtime/host/spot-address-transport.ts +4 -2
- package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
- package/src/runtime/locations/actor-location-claims.ts +52 -30
- package/src/runtime/locations/lifecycle.ts +8 -26
- package/src/runtime/locations/runtime.ts +28 -24
- package/src/runtime/messaging/framework-json-v1.ts +5 -325
- package/src/runtime/messaging/packet-name.ts +5 -25
- package/src/runtime/messaging/request-result.ts +58 -0
- package/src/runtime/messaging/submission-result.ts +5 -1
- package/src/runtime/protocol/service_wire_codec.generated.ts +131 -1
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +43 -23
- package/src/runtime/spots/index.ts +14 -2
- package/src/runtime/spots/spot-activation.ts +28 -10
- package/src/runtime/spots/spot-entry-activation.ts +9 -2
- package/src/runtime/spots/spot-handler-registry.ts +41 -8
- package/src/runtime/spots/spot-node-autoconnect.ts +6 -2
- package/src/runtime/spots/spot-outbound.ts +36 -16
- package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +2 -1
- package/src/runtime/spots/spot-runtime-ports.ts +1 -0
- package/src/runtime/spots/spot-subscription-dispatch.ts +0 -3
- package/src/runtime/spots/spot-timer.ts +5 -1
- package/src/runtime/streams/stream-session-runtime.ts +12 -29
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from './service-topology-registry';
|
|
9
9
|
import {
|
|
10
10
|
SERVICE_WIRE_PREFIX_SIZE,
|
|
11
|
+
SERVICE_WIRE_TEXT8_MAX_BYTES,
|
|
11
12
|
decodeCanonicalServiceWireText,
|
|
12
13
|
encodeCanonicalServiceWireText
|
|
13
14
|
} from './service-wire-binary-primitives';
|
|
@@ -70,8 +71,7 @@ export function encodeNodeRequestHeader(correlation: bigint): Buffer {
|
|
|
70
71
|
return withU64(prefix(M6aServiceWireCommand.nodeRequest), correlation, 'correlation');
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
export function decodeNodeRequestHeader(frame: Uint8Array): bigint {
|
|
74
|
-
const header = decodeHeader(frame);
|
|
74
|
+
export function decodeNodeRequestHeader(frame: Uint8Array, header = decodeHeader(frame)): bigint {
|
|
75
75
|
if (
|
|
76
76
|
header.command !== M6aServiceWireCommand.nodeRequest ||
|
|
77
77
|
header.flags !== 0 ||
|
|
@@ -86,8 +86,7 @@ export function encodeChannelSendHeader(channelName: string): Buffer {
|
|
|
86
86
|
return concat(prefix(M6aServiceWireCommand.channelSend), encodeText8(channelName, 'channelName'));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export function decodeChannelSendHeader(frame: Uint8Array): string {
|
|
90
|
-
const header = decodeHeader(frame);
|
|
89
|
+
export function decodeChannelSendHeader(frame: Uint8Array, header = decodeHeader(frame)): string {
|
|
91
90
|
if (header.command !== M6aServiceWireCommand.channelSend || header.flags !== 0) {
|
|
92
91
|
fail('Invalid channelSend header.');
|
|
93
92
|
}
|
|
@@ -104,11 +103,13 @@ export function encodeChannelRequestHeader(correlation: bigint, channelName: str
|
|
|
104
103
|
);
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
export function decodeChannelRequestHeader(
|
|
106
|
+
export function decodeChannelRequestHeader(
|
|
107
|
+
frame: Uint8Array,
|
|
108
|
+
header = decodeHeader(frame)
|
|
109
|
+
): {
|
|
108
110
|
readonly correlation: bigint;
|
|
109
111
|
readonly channelName: string;
|
|
110
112
|
} {
|
|
111
|
-
const header = decodeHeader(frame);
|
|
112
113
|
if (header.command !== M6aServiceWireCommand.channelRequest || header.flags !== 0) {
|
|
113
114
|
fail('Invalid channelRequest header.');
|
|
114
115
|
}
|
|
@@ -165,24 +166,12 @@ export function decodeReplyHeader(frame: Uint8Array): ServiceReplyHeader {
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
export function encodeApplicationPayload(payload: ServiceApplicationPayload): Buffer {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
packetName.byteLength + contentType.byteLength + 4 + payload.payload.byteLength;
|
|
173
|
-
if (bodyLength > MAX_U32) fail('Application payload body exceeds u32.');
|
|
174
|
-
const result = Buffer.alloc(5 + bodyLength);
|
|
175
|
-
result[0] = APPLICATION_PAYLOAD_VERSION;
|
|
176
|
-
result.writeUInt32BE(bodyLength, 1);
|
|
177
|
-
let offset = 5;
|
|
178
|
-
offset += packetName.copy(result, offset);
|
|
179
|
-
offset += contentType.copy(result, offset);
|
|
180
|
-
result.writeUInt32BE(payload.payload.byteLength, offset);
|
|
181
|
-
offset += 4;
|
|
182
|
-
Buffer.from(payload.payload.buffer, payload.payload.byteOffset, payload.payload.byteLength).copy(
|
|
183
|
-
result,
|
|
184
|
-
offset
|
|
169
|
+
const result = allocateApplicationPayload(
|
|
170
|
+
payload.packetName,
|
|
171
|
+
payload.contentType,
|
|
172
|
+
payload.payload.byteLength
|
|
185
173
|
);
|
|
174
|
+
result.set(payload.payload, result.byteLength - payload.payload.byteLength);
|
|
186
175
|
return result;
|
|
187
176
|
}
|
|
188
177
|
|
|
@@ -198,35 +187,65 @@ export function encodeMultipartApplicationPayload(
|
|
|
198
187
|
): Buffer {
|
|
199
188
|
if (parts.length === 0) fail('Multipart payload must contain at least one part.');
|
|
200
189
|
if (parts.length > MAX_U32) fail('Multipart part count exceeds u32.');
|
|
201
|
-
const packetNameBytes = encodeText8(packetName, 'packetName');
|
|
202
|
-
const contentTypeBytes = encodeText8(contentType, 'contentType');
|
|
203
190
|
let multipartLength = 4;
|
|
204
191
|
for (const part of parts) {
|
|
205
192
|
if (part.byteLength > MAX_U32) fail('Multipart part exceeds u32.');
|
|
206
193
|
multipartLength += 4 + part.byteLength;
|
|
207
194
|
if (multipartLength > MAX_U32) fail('Multipart payload exceeds u32.');
|
|
208
195
|
}
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
const result = Buffer.alloc(5 + bodyLength);
|
|
212
|
-
result[0] = APPLICATION_PAYLOAD_VERSION;
|
|
213
|
-
result.writeUInt32BE(bodyLength, 1);
|
|
214
|
-
let offset = 5;
|
|
215
|
-
offset += packetNameBytes.copy(result, offset);
|
|
216
|
-
offset += contentTypeBytes.copy(result, offset);
|
|
217
|
-
result.writeUInt32BE(multipartLength, offset);
|
|
218
|
-
offset += 4;
|
|
196
|
+
const result = allocateApplicationPayload(packetName, contentType, multipartLength);
|
|
197
|
+
let offset = result.byteLength - multipartLength;
|
|
219
198
|
result.writeUInt32BE(parts.length, offset);
|
|
220
199
|
offset += 4;
|
|
221
200
|
for (const part of parts) {
|
|
222
201
|
result.writeUInt32BE(part.byteLength, offset);
|
|
223
202
|
offset += 4;
|
|
224
|
-
|
|
203
|
+
result.set(part, offset);
|
|
225
204
|
offset += part.byteLength;
|
|
226
205
|
}
|
|
227
206
|
return result;
|
|
228
207
|
}
|
|
229
208
|
|
|
209
|
+
function allocateApplicationPayload(
|
|
210
|
+
packetName: string,
|
|
211
|
+
contentType: string,
|
|
212
|
+
payloadLength: number
|
|
213
|
+
): Buffer {
|
|
214
|
+
const packetNameBytes = encodeCanonicalServiceWireText(
|
|
215
|
+
packetName,
|
|
216
|
+
'packetName',
|
|
217
|
+
SERVICE_WIRE_TEXT8_MAX_BYTES,
|
|
218
|
+
fail
|
|
219
|
+
);
|
|
220
|
+
const contentTypeBytes = encodeCanonicalServiceWireText(
|
|
221
|
+
contentType,
|
|
222
|
+
'contentType',
|
|
223
|
+
SERVICE_WIRE_TEXT8_MAX_BYTES,
|
|
224
|
+
fail
|
|
225
|
+
);
|
|
226
|
+
if (payloadLength > MAX_U32) fail('Application payload exceeds u32.');
|
|
227
|
+
const frameLength =
|
|
228
|
+
APPLICATION_PAYLOAD_MINIMUM_FIELD_BYTES +
|
|
229
|
+
packetNameBytes.byteLength +
|
|
230
|
+
contentTypeBytes.byteLength +
|
|
231
|
+
payloadLength;
|
|
232
|
+
const prefixLength = 1 + APPLICATION_PAYLOAD_LENGTH_FIELD_BYTES;
|
|
233
|
+
const bodyLength = frameLength - prefixLength;
|
|
234
|
+
if (bodyLength > MAX_U32) fail('Application payload body exceeds u32.');
|
|
235
|
+
const result = Buffer.alloc(frameLength);
|
|
236
|
+
result[0] = APPLICATION_PAYLOAD_VERSION;
|
|
237
|
+
result.writeUInt32BE(bodyLength, 1);
|
|
238
|
+
let offset = prefixLength;
|
|
239
|
+
result[offset++] = packetNameBytes.byteLength;
|
|
240
|
+
result.set(packetNameBytes, offset);
|
|
241
|
+
offset += packetNameBytes.byteLength;
|
|
242
|
+
result[offset++] = contentTypeBytes.byteLength;
|
|
243
|
+
result.set(contentTypeBytes, offset);
|
|
244
|
+
offset += contentTypeBytes.byteLength;
|
|
245
|
+
result.writeUInt32BE(payloadLength, offset);
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
|
|
230
249
|
export function decodeApplicationPayload(frame: Uint8Array): ServiceApplicationPayload {
|
|
231
250
|
const decoded = decodeApplicationPayloadView(frame);
|
|
232
251
|
return {
|
|
@@ -487,7 +506,7 @@ function encodeU64(value: bigint): Buffer {
|
|
|
487
506
|
}
|
|
488
507
|
|
|
489
508
|
function encodeText8(value: string, field: string): Buffer {
|
|
490
|
-
const bytes = encodeCanonicalServiceWireText(value, field,
|
|
509
|
+
const bytes = encodeCanonicalServiceWireText(value, field, SERVICE_WIRE_TEXT8_MAX_BYTES, fail);
|
|
491
510
|
return concat(Buffer.of(bytes.byteLength), bytes);
|
|
492
511
|
}
|
|
493
512
|
|
|
@@ -501,7 +520,7 @@ function tlv(id: number, value: Buffer): Buffer {
|
|
|
501
520
|
}
|
|
502
521
|
|
|
503
522
|
function concat(...parts: readonly Uint8Array[]): Buffer {
|
|
504
|
-
return Buffer.concat(parts
|
|
523
|
+
return Buffer.concat(parts);
|
|
505
524
|
}
|
|
506
525
|
|
|
507
526
|
function readNonZeroU64(bytes: Buffer, offset: number, field: string): bigint {
|
|
@@ -9,13 +9,10 @@ export interface ZLinkHandlerInstanceScope {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
interface ZLinkHandlerInstanceScopeFactory {
|
|
12
|
+
prepare?(type: Type): void | (() => unknown);
|
|
12
13
|
create(context?: ZLinkMessageContext): ZLinkHandlerInstanceScope;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
interface ActiveHandlerScope {
|
|
16
|
-
readonly scope: ZLinkHandlerInstanceScope;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
16
|
interface Deferred<T> {
|
|
20
17
|
readonly promise: Promise<T>;
|
|
21
18
|
readonly resolve: (value: T) => void;
|
|
@@ -29,12 +26,37 @@ interface LifecycleScopeDisposal {
|
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
const HANDLER_SCOPE_FACTORY = Symbol.for('@zlink-systems/framework.handler-instance-scope-factory');
|
|
32
|
-
const activeHandlerScope = new AsyncLocalStorage<
|
|
29
|
+
const activeHandlerScope = new AsyncLocalStorage<ZLinkHandlerInstanceScope>();
|
|
33
30
|
const activeLifecycleScope = new AsyncLocalStorage<LifecycleHandlerInstanceScope>();
|
|
34
31
|
const detachedLifecycleResource = new AsyncResource('zlink:handler-instance-scope');
|
|
35
32
|
const dispatchScopes = new WeakMap<object, ZLinkHandlerInstanceScope>();
|
|
36
33
|
const lifecycleScopes = new WeakMap<object, LifecycleHandlerInstanceScope>();
|
|
37
34
|
|
|
35
|
+
export function registerHandlerInstanceScopeFactory(
|
|
36
|
+
providerResolver: ZLinkProviderResolver,
|
|
37
|
+
factory: ZLinkHandlerInstanceScopeFactory
|
|
38
|
+
): void {
|
|
39
|
+
Object.defineProperty(providerResolver, HANDLER_SCOPE_FACTORY, {
|
|
40
|
+
value: factory,
|
|
41
|
+
enumerable: false
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function prepareHandlerInstance<T>(
|
|
46
|
+
providerResolver: ZLinkProviderResolver | undefined,
|
|
47
|
+
type: Type<T>
|
|
48
|
+
): (() => T) | undefined {
|
|
49
|
+
if (providerResolver === undefined) return () => new type();
|
|
50
|
+
const factory = (providerResolver as unknown as Record<PropertyKey, unknown>)[
|
|
51
|
+
HANDLER_SCOPE_FACTORY
|
|
52
|
+
];
|
|
53
|
+
if (isHandlerInstanceScopeFactory(factory)) {
|
|
54
|
+
const activate = factory.prepare?.(type);
|
|
55
|
+
if (activate !== undefined) return activate as () => T;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
38
60
|
export async function runInHandlerInstanceScope<T>(
|
|
39
61
|
providerResolver: ZLinkProviderResolver | undefined,
|
|
40
62
|
context: ZLinkMessageContext | undefined,
|
|
@@ -42,7 +64,7 @@ export async function runInHandlerInstanceScope<T>(
|
|
|
42
64
|
): Promise<T> {
|
|
43
65
|
const active = activeHandlerScope.getStore();
|
|
44
66
|
if (active !== undefined) {
|
|
45
|
-
return callback(active
|
|
67
|
+
return callback(active);
|
|
46
68
|
}
|
|
47
69
|
if (context !== undefined) {
|
|
48
70
|
const existing = dispatchScopes.get(context);
|
|
@@ -56,7 +78,7 @@ export async function runInHandlerInstanceScope<T>(
|
|
|
56
78
|
dispatchScopes.set(context, scope);
|
|
57
79
|
}
|
|
58
80
|
try {
|
|
59
|
-
return await activeHandlerScope.run(
|
|
81
|
+
return await activeHandlerScope.run(scope, () => callback(scope));
|
|
60
82
|
} finally {
|
|
61
83
|
if (context !== undefined && dispatchScopes.get(context) === scope) {
|
|
62
84
|
dispatchScopes.delete(context);
|
|
@@ -144,14 +166,15 @@ class DefaultHandlerInstanceScope implements ZLinkHandlerInstanceScope {
|
|
|
144
166
|
if (existing !== undefined) return existing;
|
|
145
167
|
const activation = createDeferred<unknown>();
|
|
146
168
|
this.instances.set(type, activation.promise);
|
|
147
|
-
|
|
169
|
+
this.activate(type, activation);
|
|
148
170
|
return activation.promise as Promise<T>;
|
|
149
171
|
}
|
|
150
172
|
|
|
151
173
|
async dispose(): Promise<void> {
|
|
152
174
|
if (this.disposed) return;
|
|
153
175
|
this.disposed = true;
|
|
154
|
-
|
|
176
|
+
if (this.owned.length !== this.instances.size)
|
|
177
|
+
await Promise.allSettled(this.instances.values());
|
|
155
178
|
try {
|
|
156
179
|
await disposeOwnedInstances(this.owned);
|
|
157
180
|
} finally {
|
|
@@ -159,21 +182,43 @@ class DefaultHandlerInstanceScope implements ZLinkHandlerInstanceScope {
|
|
|
159
182
|
this.instances.clear();
|
|
160
183
|
}
|
|
161
184
|
}
|
|
162
|
-
private
|
|
163
|
-
let instance: T;
|
|
185
|
+
private activate<T>(type: Type<T>, activation: Deferred<unknown>): void {
|
|
164
186
|
try {
|
|
165
187
|
const created = this.providerResolver?.create?.(type);
|
|
166
|
-
|
|
188
|
+
if (created === undefined) {
|
|
189
|
+
this.completeActivation(new type(), activation);
|
|
190
|
+
} else {
|
|
191
|
+
void this.activatePending(created, activation);
|
|
192
|
+
}
|
|
167
193
|
} catch (error) {
|
|
168
194
|
activation.reject(error);
|
|
169
|
-
return;
|
|
170
195
|
}
|
|
196
|
+
}
|
|
171
197
|
|
|
198
|
+
private async activatePending<T>(
|
|
199
|
+
created: T | Promise<T>,
|
|
200
|
+
activation: Deferred<unknown>
|
|
201
|
+
): Promise<void> {
|
|
202
|
+
try {
|
|
203
|
+
this.completeActivation(await created, activation);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
activation.reject(error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private completeActivation(instance: unknown, activation: Deferred<unknown>): void {
|
|
172
210
|
if (!this.disposed) {
|
|
173
211
|
this.owned.push(instance);
|
|
174
212
|
activation.resolve(instance);
|
|
175
213
|
return;
|
|
176
214
|
}
|
|
215
|
+
void this.disposeLateActivation(instance, activation);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async disposeLateActivation(
|
|
219
|
+
instance: unknown,
|
|
220
|
+
activation: Deferred<unknown>
|
|
221
|
+
): Promise<void> {
|
|
177
222
|
try {
|
|
178
223
|
await disposeOwnedInstance(instance);
|
|
179
224
|
activation.reject(new Error('Handler instance scope was disposed during activation.'));
|
|
@@ -306,6 +351,11 @@ function startOutsideLifecycleInvocation<T>(work: () => T): T {
|
|
|
306
351
|
}
|
|
307
352
|
|
|
308
353
|
export async function disposeOwnedInstance(instance: unknown): Promise<void> {
|
|
354
|
+
const cleanup = handlerInstanceCleanup(instance);
|
|
355
|
+
if (cleanup !== undefined) await cleanup.call(instance);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function handlerInstanceCleanup(instance: unknown): (() => unknown) | undefined {
|
|
309
359
|
if (instance === null || instance === undefined) return;
|
|
310
360
|
const value = instance as {
|
|
311
361
|
dispose?: () => unknown;
|
|
@@ -313,12 +363,13 @@ export async function disposeOwnedInstance(instance: unknown): Promise<void> {
|
|
|
313
363
|
onModuleDestroy?: () => unknown;
|
|
314
364
|
};
|
|
315
365
|
if (typeof value.dispose === 'function') {
|
|
316
|
-
|
|
366
|
+
return value.dispose;
|
|
317
367
|
} else if (typeof value.close === 'function') {
|
|
318
|
-
|
|
368
|
+
return value.close;
|
|
319
369
|
} else if (typeof value.onModuleDestroy === 'function') {
|
|
320
|
-
|
|
370
|
+
return value.onModuleDestroy;
|
|
321
371
|
}
|
|
372
|
+
return undefined;
|
|
322
373
|
}
|
|
323
374
|
|
|
324
375
|
export async function disposeOwnedInstances(instances: readonly unknown[]): Promise<void> {
|
|
@@ -276,7 +276,15 @@ export class ZLinkActorRuntimeOptionsFactory {
|
|
|
276
276
|
staleActorRefPredicate: (meshName, actor) =>
|
|
277
277
|
this.actorBelongsToMesh(meshName, actor.actorId) &&
|
|
278
278
|
this.options.actorHandoff.isKnownStale(actor),
|
|
279
|
-
handoffCapture: (
|
|
279
|
+
handoffCapture: (
|
|
280
|
+
meshName,
|
|
281
|
+
actorId,
|
|
282
|
+
parts,
|
|
283
|
+
returnResponse,
|
|
284
|
+
actor,
|
|
285
|
+
deadlineUnixMs,
|
|
286
|
+
onAdmitted
|
|
287
|
+
) =>
|
|
280
288
|
this.actorBelongsToMesh(meshName, actorId) &&
|
|
281
289
|
!this.options.actorHandoff.isProvisional(actorId)
|
|
282
290
|
? this.options.actorHandoff.capture(
|
|
@@ -286,7 +294,10 @@ export class ZLinkActorRuntimeOptionsFactory {
|
|
|
286
294
|
returnResponse,
|
|
287
295
|
undefined,
|
|
288
296
|
actor,
|
|
289
|
-
deadlineUnixMs
|
|
297
|
+
deadlineUnixMs,
|
|
298
|
+
undefined,
|
|
299
|
+
undefined,
|
|
300
|
+
onAdmitted
|
|
290
301
|
)
|
|
291
302
|
: undefined,
|
|
292
303
|
sendErrorReporter: this.options.reportPostCommitError
|
|
@@ -15,6 +15,7 @@ import { availableParallelism } from 'node:os';
|
|
|
15
15
|
import type {
|
|
16
16
|
ApplicationJobOrigin,
|
|
17
17
|
ApplicationJobPermitPort,
|
|
18
|
+
ApplicationJobReceiveRotationPort,
|
|
18
19
|
ApplicationJobQueuePort
|
|
19
20
|
} from '../application-jobs/contracts';
|
|
20
21
|
import {
|
|
@@ -122,13 +123,42 @@ export function resolveApplicationJobQueueConfiguration(
|
|
|
122
123
|
});
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
const enum QueuePermitCount {
|
|
127
|
+
Reserved,
|
|
128
|
+
Queued,
|
|
129
|
+
Peak,
|
|
130
|
+
Size
|
|
131
|
+
}
|
|
132
|
+
|
|
125
133
|
export class ApplicationJobQueuePermit implements ApplicationJobPermitPort {
|
|
126
|
-
|
|
134
|
+
detachedForHandlerTurn = false;
|
|
135
|
+
private state: 'idle' | 'reserved' | 'queued' | 'released';
|
|
127
136
|
|
|
128
137
|
constructor(
|
|
129
138
|
private readonly owner: ApplicationJobQueue,
|
|
130
|
-
readonly origin: ApplicationJobOrigin
|
|
131
|
-
|
|
139
|
+
readonly origin: ApplicationJobOrigin,
|
|
140
|
+
private readonly reusable = false
|
|
141
|
+
) {
|
|
142
|
+
this.state = reusable ? 'idle' : 'reserved';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
reserveStorage(): boolean {
|
|
146
|
+
if (this.state !== 'idle') return false;
|
|
147
|
+
this.state = 'reserved';
|
|
148
|
+
this.detachedForHandlerTurn = false;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
releaseStorage(): void {
|
|
153
|
+
if (!this.reusable || this.state === 'idle') return;
|
|
154
|
+
if (this.state !== 'released')
|
|
155
|
+
throw new Error('Reservation storage still owns queue capacity.');
|
|
156
|
+
this.state = 'idle';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
detachForHandlerTurn(): void {
|
|
160
|
+
this.detachedForHandlerTurn = true;
|
|
161
|
+
}
|
|
132
162
|
|
|
133
163
|
markApplicationQueued(): void {
|
|
134
164
|
if (this.state !== 'reserved') {
|
|
@@ -140,17 +170,73 @@ export class ApplicationJobQueuePermit implements ApplicationJobPermitPort {
|
|
|
140
170
|
|
|
141
171
|
releaseBeforeHandler(): void {
|
|
142
172
|
if (this.owner.shouldHoldPermitBeforeHandler()) return;
|
|
143
|
-
this.
|
|
173
|
+
this.owner.releasePermit(this);
|
|
144
174
|
}
|
|
145
175
|
|
|
146
176
|
releaseAfterInternalProcessing(): void {
|
|
147
|
-
this.
|
|
177
|
+
this.owner.releasePermit(this);
|
|
148
178
|
}
|
|
149
179
|
|
|
150
|
-
|
|
151
|
-
if (this.state === 'released') return;
|
|
152
|
-
this.
|
|
180
|
+
takeReleaseState(): 'reserved' | 'queued' | undefined {
|
|
181
|
+
if (this.state === 'released' || this.state === 'idle') return undefined;
|
|
182
|
+
const state = this.state;
|
|
153
183
|
this.state = 'released';
|
|
184
|
+
return state;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Connection-owned storage; queue accounting remains with the host queue. */
|
|
189
|
+
class ApplicationJobReceiveRotation implements ApplicationJobReceiveRotationPort {
|
|
190
|
+
private readonly storage: ApplicationJobQueuePermit[] = [];
|
|
191
|
+
private readonly grants: (ApplicationJobQueuePermit | undefined)[];
|
|
192
|
+
private granted = 0;
|
|
193
|
+
private consumed = 0;
|
|
194
|
+
|
|
195
|
+
constructor(
|
|
196
|
+
private readonly owner: ApplicationJobQueue,
|
|
197
|
+
private readonly capacity: number,
|
|
198
|
+
private readonly origin: ApplicationJobOrigin
|
|
199
|
+
) {
|
|
200
|
+
this.grants = new Array<ApplicationJobQueuePermit | undefined>(capacity);
|
|
201
|
+
this.prepareStorage();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private prepareStorage(): void {
|
|
205
|
+
for (let index = 0; index < this.capacity; index++)
|
|
206
|
+
this.storage.push(new ApplicationJobQueuePermit(this.owner, this.origin, true));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
reserve(): void {
|
|
210
|
+
this.releaseUnused();
|
|
211
|
+
const count = this.owner.receiveRotationCapacity(this.capacity);
|
|
212
|
+
if (count === 0) return;
|
|
213
|
+
for (let index = 0; index < this.storage.length; index++) {
|
|
214
|
+
const permit = this.storage[index]!;
|
|
215
|
+
if (permit.reserveStorage()) this.grants[this.granted++] = permit;
|
|
216
|
+
if (this.granted === count) break;
|
|
217
|
+
}
|
|
218
|
+
if (this.granted < count) {
|
|
219
|
+
const start = this.storage.length;
|
|
220
|
+
this.prepareStorage();
|
|
221
|
+
for (let index = start; this.granted < count; index++) {
|
|
222
|
+
const permit = this.storage[index]!;
|
|
223
|
+
permit.reserveStorage();
|
|
224
|
+
this.grants[this.granted++] = permit;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
this.owner.reserveReceiveRotation(count);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
take(): ApplicationJobQueuePermit | undefined {
|
|
231
|
+
if (this.consumed === this.granted) return undefined;
|
|
232
|
+
return this.grants[this.consumed++];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
releaseUnused(): void {
|
|
236
|
+
this.owner.releaseUnusedReceiveRotation(this.grants, this.consumed, this.granted);
|
|
237
|
+
// Keep the prepared grant slots and reset only the logical count.
|
|
238
|
+
this.granted = 0;
|
|
239
|
+
this.consumed = 0;
|
|
154
240
|
}
|
|
155
241
|
}
|
|
156
242
|
|
|
@@ -173,9 +259,11 @@ interface CapacityWaiter {
|
|
|
173
259
|
export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
174
260
|
private localWaiters = 0;
|
|
175
261
|
private localBacklogLogged = false;
|
|
176
|
-
|
|
177
|
-
private
|
|
178
|
-
private
|
|
262
|
+
// The contract bounds capacity by INT32_MAX; public snapshots remain bigint.
|
|
263
|
+
private readonly permitCounts = new Uint32Array(QueuePermitCount.Size);
|
|
264
|
+
private readonly limitCount: number;
|
|
265
|
+
private readonly pauseCount: number;
|
|
266
|
+
private readonly resumeCount: number;
|
|
179
267
|
private capacityWaitCount = 0n;
|
|
180
268
|
private capacityWaitDurationSeconds = 0;
|
|
181
269
|
private metricsEpoch = 0n;
|
|
@@ -203,6 +291,9 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
|
203
291
|
private readonly handlerStartGate?: () => boolean,
|
|
204
292
|
private readonly loggerFailureReporter?: (error: unknown) => void
|
|
205
293
|
) {
|
|
294
|
+
this.limitCount = Number(configuration.effectiveMaxQueuedApplicationJobs);
|
|
295
|
+
this.pauseCount = Number(configuration.pausePermitCount);
|
|
296
|
+
this.resumeCount = Number(configuration.resumePermitCount);
|
|
206
297
|
this.receiveFlowController = new ApplicationJobReceiveFlowController(this, (state) => state);
|
|
207
298
|
}
|
|
208
299
|
|
|
@@ -210,6 +301,24 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
|
210
301
|
return this.handlerStartGate?.() === true;
|
|
211
302
|
}
|
|
212
303
|
|
|
304
|
+
createReceiveRotation(
|
|
305
|
+
capacity: number,
|
|
306
|
+
origin: ApplicationJobOrigin
|
|
307
|
+
): ApplicationJobReceiveRotationPort {
|
|
308
|
+
if (!Number.isSafeInteger(capacity) || capacity < 1)
|
|
309
|
+
throw new RangeError('Receive capacity must be positive.');
|
|
310
|
+
return new ApplicationJobReceiveRotation(this, capacity, origin);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
receiveRotationCapacity(capacity: number): number {
|
|
314
|
+
if (this.waiters.length !== 0) return 0;
|
|
315
|
+
return Math.min(capacity, this.limitCount - this.permitsInUse());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
reserveReceiveRotation(count: number): void {
|
|
319
|
+
this.reserveSupply(count);
|
|
320
|
+
}
|
|
321
|
+
|
|
213
322
|
registerReceiveFlowTarget(
|
|
214
323
|
identity: object,
|
|
215
324
|
applyState: (state: ApplicationJobQueuePressureState) => void,
|
|
@@ -272,7 +381,7 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
|
272
381
|
reject(abortReason(signal));
|
|
273
382
|
return undefined;
|
|
274
383
|
}
|
|
275
|
-
if (this.waiters.length === 0 && this.permitsInUse() < this.
|
|
384
|
+
if (this.waiters.length === 0 && this.permitsInUse() < this.limitCount) {
|
|
276
385
|
return this.reserve(origin);
|
|
277
386
|
}
|
|
278
387
|
|
|
@@ -304,10 +413,10 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
|
304
413
|
const now = this.nowMs();
|
|
305
414
|
return Object.freeze({
|
|
306
415
|
...this.configuration,
|
|
307
|
-
reservedSupplyPermits: this.
|
|
308
|
-
queuedApplicationJobs: this.
|
|
309
|
-
permitsInUse,
|
|
310
|
-
peakPermitsInUse: this.
|
|
416
|
+
reservedSupplyPermits: BigInt(this.permitCounts[QueuePermitCount.Reserved]!),
|
|
417
|
+
queuedApplicationJobs: BigInt(this.permitCounts[QueuePermitCount.Queued]!),
|
|
418
|
+
permitsInUse: BigInt(permitsInUse),
|
|
419
|
+
peakPermitsInUse: BigInt(this.permitCounts[QueuePermitCount.Peak]!),
|
|
311
420
|
capacityWaiters: BigInt(this.waiters.length),
|
|
312
421
|
capacityWaitCount: this.capacityWaitCount,
|
|
313
422
|
capacityWaitDurationSeconds: this.capacityWaitDurationSeconds,
|
|
@@ -323,7 +432,7 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
|
323
432
|
|
|
324
433
|
resetMetrics(): void {
|
|
325
434
|
this.metricsEpoch += 1n;
|
|
326
|
-
this.
|
|
435
|
+
this.permitCounts[QueuePermitCount.Peak] = this.permitsInUse();
|
|
327
436
|
this.capacityWaitCount = 0n;
|
|
328
437
|
this.capacityWaitDurationSeconds = 0;
|
|
329
438
|
this.runningTransitionCount = 0n;
|
|
@@ -357,25 +466,42 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
|
357
466
|
}
|
|
358
467
|
|
|
359
468
|
markQueued(): void {
|
|
360
|
-
if (this.
|
|
469
|
+
if (this.permitCounts[QueuePermitCount.Reserved] === 0) {
|
|
361
470
|
throw new Error('Application job queue has no reserved supply permit.');
|
|
362
471
|
}
|
|
363
|
-
this.
|
|
364
|
-
|
|
472
|
+
this.permitCounts[QueuePermitCount.Reserved] =
|
|
473
|
+
this.permitCounts[QueuePermitCount.Reserved]! - 1;
|
|
474
|
+
this.permitCounts[QueuePermitCount.Queued] = this.permitCounts[QueuePermitCount.Queued]! + 1;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
releasePermit(permit: ApplicationJobQueuePermit): void {
|
|
478
|
+
const state = permit.takeReleaseState();
|
|
479
|
+
if (state !== undefined) this.release(state);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
releaseUnusedReceiveRotation(
|
|
483
|
+
grants: readonly (ApplicationJobQueuePermit | undefined)[],
|
|
484
|
+
consumed: number,
|
|
485
|
+
granted: number
|
|
486
|
+
): void {
|
|
487
|
+
const count = granted - consumed;
|
|
488
|
+
if (count === 0) return;
|
|
489
|
+
// Unconsumed grants still belong to this receive round; no record has acquired them.
|
|
490
|
+
for (let index = consumed; index < granted; index++) {
|
|
491
|
+
const permit = grants[index]!;
|
|
492
|
+
permit.takeReleaseState();
|
|
493
|
+
permit.releaseStorage();
|
|
494
|
+
}
|
|
495
|
+
this.release('reserved', count);
|
|
365
496
|
}
|
|
366
497
|
|
|
367
|
-
release(state: 'reserved' | 'queued'): void {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
this.reservedSupplyPermits -= 1n;
|
|
373
|
-
} else {
|
|
374
|
-
if (this.queuedApplicationJobs === 0n) {
|
|
375
|
-
throw new Error('Application job queue queued permit accounting underflow.');
|
|
376
|
-
}
|
|
377
|
-
this.queuedApplicationJobs -= 1n;
|
|
498
|
+
release(state: 'reserved' | 'queued', count = 1): void {
|
|
499
|
+
const index = state === 'reserved' ? QueuePermitCount.Reserved : QueuePermitCount.Queued;
|
|
500
|
+
const available = this.permitCounts[index]!;
|
|
501
|
+
if (available < count) {
|
|
502
|
+
throw new Error(`Application job queue ${state} permit accounting underflow.`);
|
|
378
503
|
}
|
|
504
|
+
this.permitCounts[index] = available - count;
|
|
379
505
|
this.drainWaiters();
|
|
380
506
|
this.evaluatePressure();
|
|
381
507
|
}
|
|
@@ -384,23 +510,32 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
|
384
510
|
return this.configuration.effectiveMaxQueuedApplicationJobs;
|
|
385
511
|
}
|
|
386
512
|
|
|
387
|
-
private permitsInUse():
|
|
388
|
-
return
|
|
513
|
+
private permitsInUse(): number {
|
|
514
|
+
return (
|
|
515
|
+
this.permitCounts[QueuePermitCount.Reserved]! + this.permitCounts[QueuePermitCount.Queued]!
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private reserveSupply(count: number, evaluatePressure = true): void {
|
|
520
|
+
this.permitCounts[QueuePermitCount.Reserved] =
|
|
521
|
+
this.permitCounts[QueuePermitCount.Reserved]! + count;
|
|
522
|
+
this.permitCounts[QueuePermitCount.Peak] = Math.max(
|
|
523
|
+
this.permitCounts[QueuePermitCount.Peak]!,
|
|
524
|
+
this.permitsInUse()
|
|
525
|
+
);
|
|
526
|
+
if (evaluatePressure) this.evaluatePressure();
|
|
389
527
|
}
|
|
390
528
|
|
|
391
529
|
private reserve(
|
|
392
530
|
origin: ApplicationJobOrigin,
|
|
393
531
|
evaluatePressure = true
|
|
394
532
|
): ApplicationJobQueuePermit {
|
|
395
|
-
this.
|
|
396
|
-
const current = this.permitsInUse();
|
|
397
|
-
if (current > this.peakPermitsInUse) this.peakPermitsInUse = current;
|
|
398
|
-
if (evaluatePressure) this.evaluatePressure();
|
|
533
|
+
this.reserveSupply(1, evaluatePressure);
|
|
399
534
|
return new ApplicationJobQueuePermit(this, origin);
|
|
400
535
|
}
|
|
401
536
|
|
|
402
537
|
private drainWaiters(): void {
|
|
403
|
-
while (this.waiters.length > 0 && this.permitsInUse() < this.
|
|
538
|
+
while (this.waiters.length > 0 && this.permitsInUse() < this.limitCount) {
|
|
404
539
|
const waiter = this.waiters.shift()!;
|
|
405
540
|
if (!waiter.active) continue;
|
|
406
541
|
waiter.active = false;
|
|
@@ -460,13 +595,12 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
|
|
|
460
595
|
}
|
|
461
596
|
|
|
462
597
|
private evaluatePressure(): void {
|
|
463
|
-
const permitsInUse = this.permitsInUse();
|
|
464
598
|
const next =
|
|
465
599
|
this.pressureStateValue === 'running'
|
|
466
|
-
? permitsInUse >= this.
|
|
600
|
+
? this.permitsInUse() >= this.pauseCount
|
|
467
601
|
? 'paused'
|
|
468
602
|
: 'running'
|
|
469
|
-
: permitsInUse <= this.
|
|
603
|
+
: this.permitsInUse() <= this.resumeCount
|
|
470
604
|
? 'running'
|
|
471
605
|
: 'paused';
|
|
472
606
|
if (next === this.pressureStateValue) return;
|