@zlink-systems/framework 0.22.0 → 0.24.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/Dispatch/ZLinkDispatchOptions.d.ts +1 -0
- package/dist/contracts/Streams/IZLinkSession.d.ts +2 -1
- package/dist/runtime/actors/actor-context.js +4 -4
- package/dist/runtime/actors/actor-join-deferred-scope.d.ts +0 -1
- package/dist/runtime/actors/actor-join-deferred-scope.js +0 -18
- package/dist/runtime/actors/deferred-join-accepted-journal.js +2 -6
- package/dist/runtime/backend/contracts/index.d.ts +2 -0
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +4 -0
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +24 -8
- package/dist/runtime/channels/channel-dispatch-pipeline.js +2 -2
- package/dist/runtime/channels/channel-transports.js +3 -9
- package/dist/runtime/channels/dispatch-error-reporter.d.ts +1 -0
- package/dist/runtime/channels/dispatch-error-reporter.js +6 -38
- package/dist/runtime/diagnostics/dispatch-error-details.d.ts +6 -0
- package/dist/runtime/diagnostics/dispatch-error-details.js +55 -0
- package/dist/runtime/diagnostics/flow-context.d.ts +2 -1
- package/dist/runtime/diagnostics/flow-context.js +2 -2
- package/dist/runtime/diagnostics/message-flow.js +11 -3
- package/dist/runtime/foundation/actor-join-recovery-codec.js +0 -4
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +4 -2
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +34 -9
- package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +1 -0
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +5 -0
- package/dist/runtime/foundation/service-stateful-runtime.js +52 -2
- package/dist/runtime/foundation/service-topology-registry.d.ts +1 -0
- package/dist/runtime/foundation/service-topology-registry.js +12 -4
- package/dist/runtime/host/index.js +7 -3
- package/dist/runtime/locations/in-memory-location-store.js +5 -0
- package/dist/runtime/locations/location-store-repository.js +3 -1
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +20 -18
- package/dist/runtime/spots/index.js +9 -3
- package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +2 -1
- package/dist/runtime/spots/spot-actor-packet-dispatch.js +6 -1
- package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
- package/dist/runtime/spots/spot-node-runtime-manager.js +5 -1
- package/dist/runtime/spots/spot-route-packet-dispatch.js +1 -1
- package/dist/runtime/spots/spot-routed-bound-session-dispatch.js +1 -1
- package/dist/runtime/spots/spot-subscription-dispatch.js +1 -1
- package/dist/runtime/spots/spot-timer.js +1 -1
- package/dist/runtime/streams/actor-session-binding-registry.d.ts +21 -8
- package/dist/runtime/streams/actor-session-binding-registry.js +71 -42
- package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +1 -0
- package/dist/runtime/streams/bound-actor-relay-sender.js +2 -1
- package/dist/runtime/streams/bound-session-response-target.js +8 -1
- package/dist/runtime/streams/bound-session-service.d.ts +1 -0
- package/dist/runtime/streams/bound-session-service.js +6 -3
- package/dist/runtime/streams/index.js +8 -1
- package/dist/runtime/streams/managed-stream.d.ts +3 -0
- package/dist/runtime/streams/managed-stream.js +20 -1
- package/dist/runtime/streams/protocol.d.ts +5 -1
- package/dist/runtime/streams/protocol.js +41 -4
- package/dist/runtime/streams/session-actor-coordinator.js +3 -4
- package/dist/runtime/streams/session-context.d.ts +9 -2
- package/dist/runtime/streams/session-context.js +20 -4
- package/dist/runtime/streams/session-local-actors.d.ts +3 -1
- package/dist/runtime/streams/session-local-actors.js +17 -2
- package/dist/runtime/streams/stream-frame-factory.d.ts +1 -1
- package/dist/runtime/streams/stream-frame-factory.js +3 -2
- package/dist/runtime/streams/stream-session-runtime.js +42 -12
- package/package.json +3 -3
- package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +1 -0
- package/src/contracts/Streams/IZLinkSession.ts +2 -1
- package/src/runtime/actors/actor-context.ts +4 -4
- package/src/runtime/actors/actor-join-deferred-scope.ts +0 -32
- package/src/runtime/actors/deferred-join-accepted-journal.ts +2 -6
- package/src/runtime/backend/contracts/index.ts +5 -0
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +43 -11
- package/src/runtime/channels/channel-dispatch-pipeline.ts +4 -2
- package/src/runtime/channels/channel-transports.ts +3 -9
- package/src/runtime/channels/dispatch-error-reporter.ts +7 -45
- package/src/runtime/diagnostics/dispatch-error-details.ts +62 -0
- package/src/runtime/diagnostics/flow-context.ts +6 -4
- package/src/runtime/diagnostics/message-flow.ts +12 -3
- package/src/runtime/foundation/actor-join-recovery-codec.ts +0 -4
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +54 -13
- package/src/runtime/foundation/service-session-binding-ingress-port.ts +1 -0
- package/src/runtime/foundation/service-stateful-runtime.ts +71 -5
- package/src/runtime/foundation/service-topology-registry.ts +18 -5
- package/src/runtime/host/index.ts +11 -4
- package/src/runtime/locations/in-memory-location-store.ts +7 -0
- package/src/runtime/locations/location-store-repository.ts +4 -1
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +18 -17
- package/src/runtime/spots/index.ts +22 -2
- package/src/runtime/spots/spot-actor-packet-dispatch.ts +20 -3
- package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
- package/src/runtime/spots/spot-node-runtime-manager.ts +5 -1
- package/src/runtime/spots/spot-route-packet-dispatch.ts +2 -1
- package/src/runtime/spots/spot-routed-bound-session-dispatch.ts +2 -1
- package/src/runtime/spots/spot-subscription-dispatch.ts +2 -1
- package/src/runtime/spots/spot-timer.ts +6 -1
- package/src/runtime/streams/actor-session-binding-registry.ts +150 -63
- package/src/runtime/streams/actor-session-binding-runtime-owner.ts +1 -0
- package/src/runtime/streams/bound-actor-relay-sender.ts +10 -2
- package/src/runtime/streams/bound-session-response-target.ts +10 -1
- package/src/runtime/streams/bound-session-service.ts +13 -3
- package/src/runtime/streams/index.ts +8 -1
- package/src/runtime/streams/managed-stream.ts +34 -12
- package/src/runtime/streams/protocol.ts +44 -5
- package/src/runtime/streams/session-actor-coordinator.ts +12 -4
- package/src/runtime/streams/session-context.ts +31 -4
- package/src/runtime/streams/session-local-actors.ts +19 -3
- package/src/runtime/streams/stream-frame-factory.ts +4 -2
- package/src/runtime/streams/stream-session-runtime.ts +58 -13
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type ZLinkDiagnosticsContext
|
|
14
14
|
} from '../diagnostics';
|
|
15
15
|
import type { ZLinkDispatchErrorSink } from '../diagnostics/dispatch-error-port';
|
|
16
|
+
import { dispatchErrorDetails } from '../diagnostics/dispatch-error-details';
|
|
16
17
|
|
|
17
18
|
export type { ZLinkDispatchErrorSink } from '../diagnostics/dispatch-error-port';
|
|
18
19
|
|
|
@@ -46,7 +47,6 @@ export class ZLinkDispatchErrorReporter {
|
|
|
46
47
|
|
|
47
48
|
report(event: ZLinkRuntimeDispatchFailure): void {
|
|
48
49
|
const normalized = normalizeDispatchFailure(event);
|
|
49
|
-
const tracePoint = this.flow.begin(ZLinkMessageFlowOutcome.Error);
|
|
50
50
|
const dropReason = channelDropReason(normalized);
|
|
51
51
|
if (dropReason !== undefined) {
|
|
52
52
|
this.metrics?.count('zlink.mesh_node.messages.dropped', 1, {
|
|
@@ -55,8 +55,9 @@ export class ZLinkDispatchErrorReporter {
|
|
|
55
55
|
reason: dropReason
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
+
const tracePoint = this.flow.begin(ZLinkMessageFlowOutcome.Error);
|
|
58
59
|
if (tracePoint === undefined) return;
|
|
59
|
-
const errorInfo =
|
|
60
|
+
const errorInfo = dispatchErrorDetails(normalized.error);
|
|
60
61
|
this.reportedEvents += 1;
|
|
61
62
|
tracePoint.trace({
|
|
62
63
|
outcome: ZLinkMessageFlowOutcome.Error,
|
|
@@ -87,6 +88,10 @@ export class ZLinkDispatchErrorReporter {
|
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
captureEnabled(): boolean {
|
|
92
|
+
return this.flow.enabled(ZLinkMessageFlowOutcome.Error);
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
get reportedCount(): number {
|
|
91
96
|
return this.reportedEvents;
|
|
92
97
|
}
|
|
@@ -123,46 +128,3 @@ function channelDropReason(event: ZLinkRuntimeDispatchFailure): string | undefin
|
|
|
123
128
|
return undefined;
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
|
-
|
|
127
|
-
function dispatchErrorInfo(event: ZLinkRuntimeDispatchFailure): {
|
|
128
|
-
readonly errorType?: string;
|
|
129
|
-
readonly errorMessage?: string;
|
|
130
|
-
readonly errorCauseType?: string;
|
|
131
|
-
readonly errorCauseMessage?: string;
|
|
132
|
-
} {
|
|
133
|
-
if (event.errorType !== undefined || event.errorMessage !== undefined) {
|
|
134
|
-
return {
|
|
135
|
-
errorType: event.errorType,
|
|
136
|
-
errorMessage: event.errorMessage,
|
|
137
|
-
errorCauseType: event.errorCauseType,
|
|
138
|
-
errorCauseMessage: event.errorCauseMessage
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
if (event.error === undefined) {
|
|
142
|
-
return {};
|
|
143
|
-
}
|
|
144
|
-
if (event.error instanceof Error) {
|
|
145
|
-
const cause = deepestErrorCause(event.error);
|
|
146
|
-
return {
|
|
147
|
-
errorType: event.error.name,
|
|
148
|
-
errorMessage: event.error.message,
|
|
149
|
-
...(cause === event.error
|
|
150
|
-
? {}
|
|
151
|
-
: {
|
|
152
|
-
errorCauseType: cause instanceof Error ? cause.name : typeof cause,
|
|
153
|
-
errorCauseMessage: cause instanceof Error ? cause.message : String(cause)
|
|
154
|
-
})
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
return { errorType: typeof event.error, errorMessage: String(event.error) };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function deepestErrorCause(error: Error): unknown {
|
|
161
|
-
let current: unknown = error;
|
|
162
|
-
const seen = new Set<unknown>();
|
|
163
|
-
while (current instanceof Error && current.cause !== undefined && !seen.has(current)) {
|
|
164
|
-
seen.add(current);
|
|
165
|
-
current = current.cause;
|
|
166
|
-
}
|
|
167
|
-
return current;
|
|
168
|
-
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const ERROR_MESSAGE_MAX_LENGTH = 512;
|
|
2
|
+
|
|
3
|
+
const CREDENTIAL_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
4
|
+
[/Authorization\s*:\s*(?:(?:Bearer|Basic)\s+)?[^\s,;]+/gi, 'Authorization: <redacted>'],
|
|
5
|
+
[/Bearer\s+[^\s,;]+/gi, 'Bearer <redacted>'],
|
|
6
|
+
[/password\s*=\s*[^\s,;]+/gi, 'password=<redacted>'],
|
|
7
|
+
[/token\s*=\s*[^\s,;]+/gi, 'token=<redacted>']
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export function dispatchErrorDetails(error: unknown): {
|
|
11
|
+
readonly errorType?: string;
|
|
12
|
+
readonly errorMessage?: string;
|
|
13
|
+
readonly errorCauseType?: string;
|
|
14
|
+
readonly errorCauseMessage?: string;
|
|
15
|
+
} {
|
|
16
|
+
if (error === undefined || error === null) return {};
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
const cause = deepestErrorCause(error);
|
|
19
|
+
return {
|
|
20
|
+
errorType: error.name,
|
|
21
|
+
errorMessage: sanitizeErrorMessage(error.message),
|
|
22
|
+
...(cause === error
|
|
23
|
+
? {}
|
|
24
|
+
: {
|
|
25
|
+
errorCauseType: cause instanceof Error ? cause.name : typeof cause,
|
|
26
|
+
errorCauseMessage: sanitizeErrorMessage(
|
|
27
|
+
cause instanceof Error ? cause.message : String(cause)
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const errorType = typeof error === 'object' ? error.constructor.name : typeof error;
|
|
33
|
+
const message =
|
|
34
|
+
typeof error === 'string' ||
|
|
35
|
+
typeof error === 'number' ||
|
|
36
|
+
typeof error === 'boolean' ||
|
|
37
|
+
typeof error === 'bigint'
|
|
38
|
+
? `${error}`
|
|
39
|
+
: Object.prototype.toString.call(error);
|
|
40
|
+
return { errorType, errorMessage: sanitizeErrorMessage(message) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function deepestErrorCause(error: Error): unknown {
|
|
44
|
+
let current: unknown = error;
|
|
45
|
+
const seen = new Set<unknown>();
|
|
46
|
+
while (current instanceof Error && current.cause !== undefined && !seen.has(current)) {
|
|
47
|
+
seen.add(current);
|
|
48
|
+
current = current.cause;
|
|
49
|
+
}
|
|
50
|
+
return current;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sanitizeErrorMessage(message: string): string {
|
|
54
|
+
if (message.length === 0) return '';
|
|
55
|
+
const lineEnd = message.search(/[\r\n]/u);
|
|
56
|
+
let sanitized = lineEnd < 0 ? message : message.slice(0, lineEnd);
|
|
57
|
+
for (const [pattern, replacement] of CREDENTIAL_PATTERNS) {
|
|
58
|
+
pattern.lastIndex = 0;
|
|
59
|
+
sanitized = sanitized.replace(pattern, replacement);
|
|
60
|
+
}
|
|
61
|
+
return sanitized.slice(0, ERROR_MESSAGE_MAX_LENGTH);
|
|
62
|
+
}
|
|
@@ -5,6 +5,7 @@ import type { ZLinkFlowOrigin } from '../../contracts';
|
|
|
5
5
|
export interface ZLinkFlowContextValue {
|
|
6
6
|
readonly flowId: string;
|
|
7
7
|
readonly flowOrigin: ZLinkFlowOrigin;
|
|
8
|
+
readonly streamSessionId?: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
const flowStorage = new AsyncLocalStorage<ZLinkFlowContextValue | undefined>();
|
|
@@ -71,14 +72,15 @@ export function runWithOutboundFlow<T>(enabled: boolean, callback: () => T): T {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
export function createInboundFlow(
|
|
74
|
-
flowId
|
|
75
|
-
flowOrigin
|
|
76
|
-
createIfAbsent
|
|
75
|
+
flowId: string | undefined,
|
|
76
|
+
flowOrigin: ZLinkFlowOrigin | undefined,
|
|
77
|
+
createIfAbsent: boolean,
|
|
78
|
+
streamSessionId: string | undefined
|
|
77
79
|
): ZLinkFlowContextValue | undefined {
|
|
78
80
|
if (!createIfAbsent) {
|
|
79
81
|
return undefined;
|
|
80
82
|
}
|
|
81
|
-
return { flowId: flowId ?? createFlowId(), flowOrigin: flowOrigin ?? 'Inbound' };
|
|
83
|
+
return { flowId: flowId ?? createFlowId(), flowOrigin: flowOrigin ?? 'Inbound', streamSessionId };
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
function createFlowId(): string {
|
|
@@ -34,6 +34,7 @@ export const DEFAULT_ZLINK_DIAGNOSTICS: ZLinkDiagnosticsOptions = {
|
|
|
34
34
|
|
|
35
35
|
const telemetryLogger = openTelemetryLogs.getLogger('@zlink-systems/framework');
|
|
36
36
|
const telemetryTracer = openTelemetryTrace.getTracer('@zlink-systems/framework');
|
|
37
|
+
const ERROR_MESSAGE_MAX_LENGTH = 512;
|
|
37
38
|
let diagnosticsGeneration = 0n;
|
|
38
39
|
|
|
39
40
|
interface ZLinkTelemetryRecord {
|
|
@@ -68,6 +69,7 @@ interface ZLinkTelemetryRecord {
|
|
|
68
69
|
readonly instanceSpotType?: string;
|
|
69
70
|
readonly activationState?: string;
|
|
70
71
|
readonly actorId?: string;
|
|
72
|
+
readonly streamSessionId?: string;
|
|
71
73
|
readonly commandId?: number;
|
|
72
74
|
readonly messageSizeBytes?: number;
|
|
73
75
|
readonly durationSeconds?: number;
|
|
@@ -226,12 +228,13 @@ export class ZLinkMessageFlowTracer {
|
|
|
226
228
|
acceptedFlowId?: string,
|
|
227
229
|
acceptedFlowOrigin?: ZLinkFlowOrigin
|
|
228
230
|
): void {
|
|
229
|
-
const ambient =
|
|
231
|
+
const ambient = currentFlowContext();
|
|
230
232
|
const flowId = flowInput.flowId ?? acceptedFlowId ?? ambient?.flowId;
|
|
231
233
|
const flowOrigin = flowInput.flowOrigin ?? acceptedFlowOrigin ?? ambient?.flowOrigin;
|
|
232
234
|
const flow: ZLinkRuntimeMessageFlowEvent = {
|
|
233
235
|
...flowInput,
|
|
234
236
|
...(flowId === undefined ? {} : { flowId, flowOrigin }),
|
|
237
|
+
streamSessionId: flowInput.streamSessionId ?? ambient?.streamSessionId,
|
|
235
238
|
effectiveMode
|
|
236
239
|
};
|
|
237
240
|
this.tracedEvents += 1;
|
|
@@ -380,11 +383,12 @@ function toTelemetryRecord(
|
|
|
380
383
|
instanceSpotType: flow.instanceSpotType,
|
|
381
384
|
activationState: flow.activationState,
|
|
382
385
|
actorId: flow.actorId,
|
|
386
|
+
streamSessionId: flow.streamSessionId,
|
|
383
387
|
commandId: flow.commandId,
|
|
384
388
|
messageSizeBytes,
|
|
385
389
|
durationSeconds,
|
|
386
390
|
errorType: flow.errorType,
|
|
387
|
-
errorMessage:
|
|
391
|
+
errorMessage: flow.errorMessage,
|
|
388
392
|
errorCauseType: flow.errorCauseType,
|
|
389
393
|
errorCauseMessage: boundedText(flow.errorCauseMessage)
|
|
390
394
|
};
|
|
@@ -421,6 +425,7 @@ function structuredLogAttributes(record: ZLinkTelemetryRecord): Attributes {
|
|
|
421
425
|
instance_spot_type: record.instanceSpotType,
|
|
422
426
|
activation_state: record.activationState,
|
|
423
427
|
actor_id: record.actorId,
|
|
428
|
+
stream_session_id: record.streamSessionId,
|
|
424
429
|
correlation_id: record.correlationId,
|
|
425
430
|
flow_id: record.flowId,
|
|
426
431
|
flow_origin: record.flowOrigin,
|
|
@@ -456,11 +461,15 @@ function structuredLogBodyAttributes(record: ZLinkTelemetryRecord): Attributes {
|
|
|
456
461
|
instance_type: record.instanceSpotType,
|
|
457
462
|
activation_state: record.activationState,
|
|
458
463
|
actor: record.actorId,
|
|
464
|
+
session: record.streamSessionId,
|
|
459
465
|
corr: record.correlationId,
|
|
460
466
|
flow: record.flowId,
|
|
461
467
|
origin: record.flowOrigin,
|
|
462
468
|
outcome: record.outcome,
|
|
463
469
|
reason: record.reason,
|
|
470
|
+
action: record.action,
|
|
471
|
+
error_type: record.errorType,
|
|
472
|
+
error_message: record.errorMessage,
|
|
464
473
|
size: record.messageSizeBytes
|
|
465
474
|
});
|
|
466
475
|
}
|
|
@@ -571,7 +580,7 @@ function hashFlowId(flowId: string): number {
|
|
|
571
580
|
}
|
|
572
581
|
|
|
573
582
|
function boundedText(value: string | undefined): string | undefined {
|
|
574
|
-
return value === undefined ? undefined : value.slice(0,
|
|
583
|
+
return value === undefined ? undefined : value.slice(0, ERROR_MESSAGE_MAX_LENGTH);
|
|
575
584
|
}
|
|
576
585
|
|
|
577
586
|
function toSnakeCase(value: string): string {
|
|
@@ -10,7 +10,6 @@ import { decodeServiceWireFrozenRecord } from './service-stateful-wire-codec';
|
|
|
10
10
|
const RECOVERY_PACKET_NAME = '__zlink.actor.routed_join.recovery';
|
|
11
11
|
const RECOVERY_CONTENT_TYPE = 'application/x-zlink-actor-routed-join-recovery-v1';
|
|
12
12
|
const MAXIMUM_METADATA_BYTES = 256 * 1024;
|
|
13
|
-
const MAXIMUM_MESSAGE_BYTES = 1024 * 1024;
|
|
14
13
|
const FRAMEWORK_METADATA_UPPER_BOUND_BYTES = 64 * 1024;
|
|
15
14
|
const ACCEPTED_JOURNAL_UPPER_BOUND_BYTES = 16 * 1024 * 1024;
|
|
16
15
|
const SNAPSHOT_APPLICATION_STATE_RESERVATION_BYTES = 64 * 1024 * 1024;
|
|
@@ -124,9 +123,6 @@ export function encodeCanonicalActorJoinRecoverySavedWork(
|
|
|
124
123
|
const operationId = input.operationId;
|
|
125
124
|
const reply = operationId === undefined ? Buffer.alloc(0) : Buffer.from(input.reply ?? []);
|
|
126
125
|
const request = Buffer.from(input.request);
|
|
127
|
-
if (request.byteLength > MAXIMUM_MESSAGE_BYTES || reply.byteLength > MAXIMUM_MESSAGE_BYTES) {
|
|
128
|
-
throw new RangeError('Actor Join recovery message exceeds 1 MiB.');
|
|
129
|
-
}
|
|
130
126
|
const replyContentType =
|
|
131
127
|
operationId === undefined
|
|
132
128
|
? null
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
ZLinkRawReceivedRecord,
|
|
8
8
|
ZLinkRawRouterPort
|
|
9
9
|
} from '../backend/raw-binding-port';
|
|
10
|
-
import { RequestResult } from '../backend/runtime-values';
|
|
10
|
+
import { RequestResult, SubmitResult } from '../backend/runtime-values';
|
|
11
11
|
import type {
|
|
12
12
|
ApplicationJobPermitPort,
|
|
13
13
|
ApplicationJobQueuePort
|
|
@@ -63,6 +63,19 @@ export interface RawServiceRequestResult {
|
|
|
63
63
|
readonly payload?: ServiceApplicationPayload;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
type RawServiceChannelTargetSelection =
|
|
67
|
+
| { readonly kind: 'selected'; readonly peer: AdmittedServicePeer }
|
|
68
|
+
| {
|
|
69
|
+
readonly kind: 'noMember';
|
|
70
|
+
readonly submitResult: typeof SubmitResult.NotFound;
|
|
71
|
+
readonly requestResult: RawServiceRequestResult;
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
readonly kind: 'knownButNotReady';
|
|
75
|
+
readonly submitResult: typeof SubmitResult.NotConnected;
|
|
76
|
+
readonly requestResult: RawServiceRequestResult;
|
|
77
|
+
};
|
|
78
|
+
|
|
66
79
|
/** An M6A application frame already owned by the Framework runtime. */
|
|
67
80
|
type ServiceApplicationPayloadInput = ServiceApplicationPayload | Buffer;
|
|
68
81
|
|
|
@@ -463,11 +476,10 @@ export class RawServiceMeshRuntime {
|
|
|
463
476
|
async sendToChannel(
|
|
464
477
|
channelName: string,
|
|
465
478
|
payload: ServiceApplicationPayloadInput
|
|
466
|
-
): Promise<
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (selected === undefined) return false;
|
|
479
|
+
): Promise<SubmitResult> {
|
|
480
|
+
const selection = this.selectChannelTarget(channelName);
|
|
481
|
+
if (selection.kind !== 'selected') return selection.submitResult;
|
|
482
|
+
const selected = selection.peer;
|
|
471
483
|
const applicationFrame = this.applicationFrame(payload);
|
|
472
484
|
if (selected.descriptor.nodeRoutingId === this.descriptor.nodeRoutingId) {
|
|
473
485
|
const applicationJobOwner = await this.reserveLocalIngress();
|
|
@@ -481,15 +493,17 @@ export class RawServiceMeshRuntime {
|
|
|
481
493
|
applicationJob
|
|
482
494
|
});
|
|
483
495
|
if (!accepted) applicationJob.close();
|
|
484
|
-
return accepted;
|
|
496
|
+
return accepted ? SubmitResult.Ok : SubmitResult.NotAdmitted;
|
|
485
497
|
} finally {
|
|
486
498
|
applicationJobOwner.close();
|
|
487
499
|
}
|
|
488
500
|
}
|
|
489
|
-
return this.send(selected.descriptor.nodeRoutingId, [
|
|
501
|
+
return (await this.send(selected.descriptor.nodeRoutingId, [
|
|
490
502
|
encodeChannelSendHeader(channelName),
|
|
491
503
|
applicationFrame
|
|
492
|
-
])
|
|
504
|
+
]))
|
|
505
|
+
? SubmitResult.Ok
|
|
506
|
+
: SubmitResult.NotConnected;
|
|
493
507
|
}
|
|
494
508
|
|
|
495
509
|
requestToNode(
|
|
@@ -504,13 +518,40 @@ export class RawServiceMeshRuntime {
|
|
|
504
518
|
channelName: string,
|
|
505
519
|
payload: ServiceApplicationPayloadInput,
|
|
506
520
|
timeoutMs: number
|
|
507
|
-
): PendingOperation<RawServiceRequestResult>
|
|
521
|
+
): PendingOperation<RawServiceRequestResult> {
|
|
522
|
+
const selection = this.selectChannelTarget(channelName);
|
|
523
|
+
if (selection.kind !== 'selected') {
|
|
524
|
+
const pending = this.operations.register(timeoutMs);
|
|
525
|
+
this.operations.complete(pending.id, selection.requestResult);
|
|
526
|
+
return pending;
|
|
527
|
+
}
|
|
528
|
+
return this.requestToTarget(
|
|
529
|
+
selection.peer.descriptor.nodeRoutingId,
|
|
530
|
+
payload,
|
|
531
|
+
timeoutMs,
|
|
532
|
+
channelName
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private selectChannelTarget(channelName: string): RawServiceChannelTargetSelection {
|
|
508
537
|
const selected = this.topology.selectChannel(channelName, (peer) =>
|
|
509
538
|
this.isLocalOrReadyPeer(peer.descriptor.nodeRoutingId)
|
|
510
539
|
);
|
|
511
|
-
return selected
|
|
512
|
-
|
|
513
|
-
|
|
540
|
+
if (selected !== undefined) return { kind: 'selected', peer: selected };
|
|
541
|
+
return this.topology.hasKnownChannelTarget(channelName)
|
|
542
|
+
? {
|
|
543
|
+
kind: 'knownButNotReady',
|
|
544
|
+
submitResult: SubmitResult.NotConnected,
|
|
545
|
+
requestResult: { terminalResult: RequestResult.NotConnected, failureCode: 0 }
|
|
546
|
+
}
|
|
547
|
+
: {
|
|
548
|
+
kind: 'noMember',
|
|
549
|
+
submitResult: SubmitResult.NotFound,
|
|
550
|
+
requestResult: {
|
|
551
|
+
terminalResult: RequestResult.NotFound,
|
|
552
|
+
failureCode: REQUEST_TARGET_NOT_FOUND_FAILURE_CODE
|
|
553
|
+
}
|
|
554
|
+
};
|
|
514
555
|
}
|
|
515
556
|
|
|
516
557
|
private isLocalOrReadyPeer(nodeRoutingId: string): boolean {
|
|
@@ -19,6 +19,7 @@ export interface ServiceSessionBindingAdmissionClaim {
|
|
|
19
19
|
export type ServiceSessionBindingAdmissionResult = 'passThrough' | 'retained' | 'rejected';
|
|
20
20
|
|
|
21
21
|
export interface ServiceSessionBindingIngressPort {
|
|
22
|
+
actorSlot(actorId: string, sessionRid: string): Promise<number | undefined>;
|
|
22
23
|
retainOutbound(
|
|
23
24
|
claim: ServiceSessionBindingAdmissionClaim,
|
|
24
25
|
delivery: ServiceSessionBindingRetainedDelivery
|
|
@@ -9,6 +9,12 @@ import {
|
|
|
9
9
|
translateWireReplyDecodeError
|
|
10
10
|
} from '../framework-errors-internal';
|
|
11
11
|
import { RequestResult, SubmitResult, isZLinkBackendResultError } from '../backend/runtime-values';
|
|
12
|
+
import type { ZLinkDispatchErrorReporter } from '../channels/dispatch-error-reporter';
|
|
13
|
+
import {
|
|
14
|
+
ZLinkRuntimeDispatchErrorAction as ZLinkDispatchErrorAction,
|
|
15
|
+
ZLinkDispatchErrorSurface,
|
|
16
|
+
ZLinkDispatchMessageKind
|
|
17
|
+
} from '../../contracts/Dispatch/ZLinkDispatchOptions';
|
|
12
18
|
import type {
|
|
13
19
|
RawServiceIngressRecord,
|
|
14
20
|
RawServiceMeshRuntime,
|
|
@@ -401,6 +407,8 @@ export class ServiceStatefulRuntime {
|
|
|
401
407
|
readonly kind: 'spot_multicast' | 'actor_control' | 'actor_binding';
|
|
402
408
|
readonly owner: string;
|
|
403
409
|
}) => void;
|
|
410
|
+
private dispatchErrors?: ZLinkDispatchErrorReporter;
|
|
411
|
+
private dispatchErrorMeshName?: string;
|
|
404
412
|
private readonly admittedUserSpotOperations = new Map<
|
|
405
413
|
string,
|
|
406
414
|
{
|
|
@@ -438,6 +446,11 @@ export class ServiceStatefulRuntime {
|
|
|
438
446
|
this.mailboxDropHandler = handler;
|
|
439
447
|
}
|
|
440
448
|
|
|
449
|
+
setDispatchErrorReporter(reporter: ZLinkDispatchErrorReporter, meshName: string): void {
|
|
450
|
+
this.dispatchErrors = reporter;
|
|
451
|
+
this.dispatchErrorMeshName = meshName;
|
|
452
|
+
}
|
|
453
|
+
|
|
441
454
|
createSpot(
|
|
442
455
|
routingId?: string,
|
|
443
456
|
kind: 'user' | 'instance' = 'user',
|
|
@@ -673,6 +686,13 @@ export class ServiceStatefulRuntime {
|
|
|
673
686
|
}
|
|
674
687
|
}
|
|
675
688
|
|
|
689
|
+
completeClosedInstance(spotId: string, objectGeneration: bigint): void {
|
|
690
|
+
const current = this.instanceIntents.get(spotId);
|
|
691
|
+
if (current !== undefined && current.route.objectGeneration === objectGeneration) {
|
|
692
|
+
this.forgetClosedInstanceRoute(current.route);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
676
696
|
registerInstanceActivationAuthority(authority: ServiceInstanceActivationAuthority): void {
|
|
677
697
|
this.requireOpen();
|
|
678
698
|
if (
|
|
@@ -1229,17 +1249,41 @@ export class ServiceStatefulRuntime {
|
|
|
1229
1249
|
await this.enqueueLogicalMulticast(channelName, topic, sourceSpotId, payload);
|
|
1230
1250
|
const targets = this.raw.topology
|
|
1231
1251
|
.peers()
|
|
1232
|
-
.filter(
|
|
1233
|
-
peer
|
|
1234
|
-
|
|
1235
|
-
|
|
1252
|
+
.filter(
|
|
1253
|
+
(peer) =>
|
|
1254
|
+
peer.descriptor.channels.some(
|
|
1255
|
+
(channel) => channel.name === channelName && channel.weight > 0
|
|
1256
|
+
) && this.raw.isPeerRouteReady(peer.descriptor.nodeRoutingId)
|
|
1236
1257
|
);
|
|
1237
1258
|
const header = encodeLogicalMulticastHeader(channelName, topic, sourceSpotId);
|
|
1238
1259
|
const payloadFrame = encodeApplicationPayload(payload);
|
|
1260
|
+
const reporter = this.dispatchErrors;
|
|
1261
|
+
const captureFailures = reporter?.captureEnabled() === true;
|
|
1239
1262
|
for (const target of targets) {
|
|
1240
1263
|
// Remote admission ends at the source outbound transport queue. The
|
|
1241
1264
|
// receiver's Spot queue and handler completion are not publish results.
|
|
1242
|
-
await this.raw.sendService(target.descriptor.nodeRoutingId, [
|
|
1265
|
+
const accepted = await this.raw.sendService(target.descriptor.nodeRoutingId, [
|
|
1266
|
+
header,
|
|
1267
|
+
payloadFrame
|
|
1268
|
+
]);
|
|
1269
|
+
if (!accepted) {
|
|
1270
|
+
if (captureFailures) {
|
|
1271
|
+
reporter.report({
|
|
1272
|
+
surface: ZLinkDispatchErrorSurface.SpotRoute,
|
|
1273
|
+
messageKind: ZLinkDispatchMessageKind.Send,
|
|
1274
|
+
reason: serviceSpotPublishFailureReason(
|
|
1275
|
+
undefined,
|
|
1276
|
+
this.raw.isPeerRouteReady(target.descriptor.nodeRoutingId),
|
|
1277
|
+
this.closed
|
|
1278
|
+
),
|
|
1279
|
+
action: ZLinkDispatchErrorAction.Drop,
|
|
1280
|
+
meshName: this.dispatchErrorMeshName,
|
|
1281
|
+
channelName,
|
|
1282
|
+
topic,
|
|
1283
|
+
targetRid: target.descriptor.nodeRoutingId
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1243
1287
|
}
|
|
1244
1288
|
}
|
|
1245
1289
|
|
|
@@ -5251,6 +5295,28 @@ export class ServiceStatefulRuntime {
|
|
|
5251
5295
|
}
|
|
5252
5296
|
}
|
|
5253
5297
|
|
|
5298
|
+
function serviceSpotPublishFailureReason(
|
|
5299
|
+
result: number | undefined,
|
|
5300
|
+
routeReady: boolean,
|
|
5301
|
+
closed: boolean
|
|
5302
|
+
): 'stale_target' | 'backpressure' | 'shutdown' {
|
|
5303
|
+
if (closed || result === SubmitResult.Terminated) return 'shutdown';
|
|
5304
|
+
switch (result) {
|
|
5305
|
+
case SubmitResult.Backpressured:
|
|
5306
|
+
return 'backpressure';
|
|
5307
|
+
case SubmitResult.NotAdmitted:
|
|
5308
|
+
case SubmitResult.NotConnected:
|
|
5309
|
+
case SubmitResult.NotFound:
|
|
5310
|
+
case SubmitResult.InvalidHandle:
|
|
5311
|
+
return 'stale_target';
|
|
5312
|
+
default:
|
|
5313
|
+
// RawServiceMeshRuntime exposes failed Core submissions as false. A
|
|
5314
|
+
// ready route therefore identifies queue admission failure; a route
|
|
5315
|
+
// that became unready after selection is a stale target.
|
|
5316
|
+
return routeReady ? 'backpressure' : 'stale_target';
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5254
5320
|
function isEntrySpotFence(fence: ServiceDirectSpotRouteFence): boolean {
|
|
5255
5321
|
return (
|
|
5256
5322
|
fence.spot.spotId === fence.targetNodeRid &&
|
|
@@ -257,6 +257,13 @@ export class ServiceTopologyRegistry {
|
|
|
257
257
|
return descriptor === undefined ? undefined : cloneDescriptor(descriptor);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
hasKnownChannelTarget(channelName: string): boolean {
|
|
261
|
+
requireText(channelName, 'channelName');
|
|
262
|
+
return [this.local, ...this.knownByRid.values()].some(
|
|
263
|
+
(descriptor) => selectableChannel(descriptor, channelName) !== undefined
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
260
267
|
private remember(descriptor: ServiceNodeDescriptor): void {
|
|
261
268
|
const current = this.knownByRid.get(descriptor.nodeRoutingId);
|
|
262
269
|
if (
|
|
@@ -285,7 +292,7 @@ export class ServiceTopologyRegistry {
|
|
|
285
292
|
return [local, ...this.peersByRid.values()]
|
|
286
293
|
.map((peer) => ({
|
|
287
294
|
peer: clonePeer(peer),
|
|
288
|
-
channel:
|
|
295
|
+
channel: selectableChannel(peer.descriptor, channelName)
|
|
289
296
|
}))
|
|
290
297
|
.filter(
|
|
291
298
|
(
|
|
@@ -293,10 +300,7 @@ export class ServiceTopologyRegistry {
|
|
|
293
300
|
): value is {
|
|
294
301
|
peer: AdmittedServicePeer;
|
|
295
302
|
channel: ServiceChannelDescriptor;
|
|
296
|
-
} =>
|
|
297
|
-
value.channel !== undefined &&
|
|
298
|
-
value.peer.descriptor.state === 'serving' &&
|
|
299
|
-
value.channel.weight > 0
|
|
303
|
+
} => value.channel !== undefined
|
|
300
304
|
);
|
|
301
305
|
},
|
|
302
306
|
(value) => value.channel.weight,
|
|
@@ -519,6 +523,15 @@ function findChannel(
|
|
|
519
523
|
return descriptor.channels.find((channel) => channel.name === channelName);
|
|
520
524
|
}
|
|
521
525
|
|
|
526
|
+
function selectableChannel(
|
|
527
|
+
descriptor: ServiceNodeDescriptor,
|
|
528
|
+
channelName: string
|
|
529
|
+
): ServiceChannelDescriptor | undefined {
|
|
530
|
+
if (descriptor.state !== 'serving') return undefined;
|
|
531
|
+
const channel = findChannel(descriptor, channelName);
|
|
532
|
+
return channel !== undefined && channel.weight > 0 ? channel : undefined;
|
|
533
|
+
}
|
|
534
|
+
|
|
522
535
|
function requireText(value: string, field: string): void {
|
|
523
536
|
if (value.length === 0 || value.includes('\0')) {
|
|
524
537
|
throw new TypeError(`${field} must be non-empty text without NUL.`);
|
|
@@ -1982,9 +1982,16 @@ export class ZLinkFrameworkRuntimeHost
|
|
|
1982
1982
|
// coordinator after application initialization. The legacy location
|
|
1983
1983
|
// claim would expose a Creating object before that Ready barrier.
|
|
1984
1984
|
locationLifecycle: () => undefined,
|
|
1985
|
-
releaseInstanceAuthority: (meshName, spotId, objectGeneration) =>
|
|
1986
|
-
this.locationOwner.currentLifecycle?.releaseSpot(
|
|
1987
|
-
|
|
1985
|
+
releaseInstanceAuthority: async (meshName, spotId, objectGeneration) => {
|
|
1986
|
+
await this.locationOwner.currentLifecycle?.releaseSpot(
|
|
1987
|
+
meshName,
|
|
1988
|
+
spotId,
|
|
1989
|
+
objectGeneration
|
|
1990
|
+
);
|
|
1991
|
+
this.spotNodeRuntime
|
|
1992
|
+
?.meshNode(meshName)
|
|
1993
|
+
?.completeClosedInstance?.(spotId, objectGeneration);
|
|
1994
|
+
},
|
|
1988
1995
|
beginInstanceIdleClosingAuthority: (meshName, spotId) =>
|
|
1989
1996
|
this.locationOwner.currentLifecycle?.beginInstanceSpotClosing(meshName, spotId) ??
|
|
1990
1997
|
Promise.resolve(undefined),
|
|
@@ -3047,7 +3054,7 @@ export class ZLinkFrameworkRuntimeHost
|
|
|
3047
3054
|
const flow =
|
|
3048
3055
|
current?.flowOrigin === 'Lifecycle'
|
|
3049
3056
|
? current
|
|
3050
|
-
: createInboundFlow(undefined, 'Lifecycle', this.flowCreationEnabled());
|
|
3057
|
+
: createInboundFlow(undefined, 'Lifecycle', this.flowCreationEnabled(), undefined);
|
|
3051
3058
|
return runWithFlow(flow, operation);
|
|
3052
3059
|
}
|
|
3053
3060
|
|
|
@@ -278,6 +278,13 @@ export class ZLinkInMemoryLocationStore
|
|
|
278
278
|
) {
|
|
279
279
|
return stored(this.meshNodes.generations.get(key) ?? 1n, current.updatedAt);
|
|
280
280
|
}
|
|
281
|
+
if (
|
|
282
|
+
intent === ZLinkLocationWriteIntent.NewClaim &&
|
|
283
|
+
currentLease !== undefined &&
|
|
284
|
+
currentLease.leaseExpiresAt.getTime() > updatedAt.getTime()
|
|
285
|
+
) {
|
|
286
|
+
return rejectedConflict();
|
|
287
|
+
}
|
|
281
288
|
if (
|
|
282
289
|
current.ownerId !== descriptor.ownerId ||
|
|
283
290
|
current.leaseGeneration !== descriptor.leaseGeneration ||
|
|
@@ -2329,7 +2329,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2329
2329
|
const renew = intent === 2 && canRenew(stored, descriptor);
|
|
2330
2330
|
if (!takeover && !renew) {
|
|
2331
2331
|
return {
|
|
2332
|
-
status:
|
|
2332
|
+
status:
|
|
2333
|
+
intent === ZLinkLocationWriteIntent.NewClaim && currentLeaseGeneration !== undefined
|
|
2334
|
+
? WriteStatus.RejectedConflict
|
|
2335
|
+
: WriteStatus.IgnoredStale,
|
|
2333
2336
|
generation,
|
|
2334
2337
|
updatedAt: current.value.storeNow
|
|
2335
2338
|
};
|