@zlink-systems/framework 0.28.0 → 0.29.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/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Configuration/Builders.d.ts +2 -0
- package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
- package/dist/contracts/Configuration/InternalDefaults.js +4 -1
- package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
- package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
- package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -0
- package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
- package/dist/runtime/actors/actor-mailbox.js +2 -2
- package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
- package/dist/runtime/actors/index.d.ts +2 -0
- package/dist/runtime/actors/index.js +21 -6
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +1 -3
- package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
- package/dist/runtime/execution/serial-execution-queue.js +116 -28
- package/dist/runtime/foundation/operation-registry.d.ts +3 -2
- package/dist/runtime/foundation/operation-registry.js +5 -1
- package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
- package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
- package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
- package/dist/runtime/foundation/service-stateful-registry.js +5 -2
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
- package/dist/runtime/foundation/service-stateful-runtime.js +105 -145
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
- package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
- package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
- package/dist/runtime/foundation/service-topology-registry.js +16 -17
- package/dist/runtime/handlers/handler-instance-scope.js +1 -1
- package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
- package/dist/runtime/host/actor-placement-coordinator.js +2 -1
- package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
- package/dist/runtime/host/actor-runtime-options-factory.js +1 -0
- package/dist/runtime/host/index.d.ts +1 -1
- package/dist/runtime/host/index.js +3 -2
- package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
- package/dist/runtime/host/instance-activation-authority.js +46 -97
- package/dist/runtime/host/spot-address-transport.js +1 -1
- package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
- package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
- package/dist/runtime/locations/domain-store-contract.d.ts +3 -1
- package/dist/runtime/locations/in-memory-authority-store.d.ts +3 -0
- package/dist/runtime/locations/in-memory-authority-store.js +47 -12
- package/dist/runtime/locations/in-memory-location-store.d.ts +2 -1
- package/dist/runtime/locations/in-memory-location-store.js +15 -10
- package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
- package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
- package/dist/runtime/locations/location-store-repository.d.ts +2 -0
- package/dist/runtime/locations/location-store-repository.js +130 -54
- package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
- package/dist/runtime/spots/spot-entry-activation.js +3 -0
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +1 -0
- package/dist/runtime/spots/spot-node-runtime-manager.js +6 -0
- package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
- package/dist/runtime/spots/spot-serial-executor.js +14 -2
- package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
- package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
- package/dist/runtime/streams/index.d.ts +0 -1
- package/dist/runtime/streams/index.js +4 -3
- package/dist/runtime/streams/protocol.js +0 -9
- package/dist/runtime/streams/session-context.d.ts +0 -4
- package/dist/runtime/streams/session-context.js +0 -26
- package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
- package/dist/runtime/streams/session-serial-executor.js +0 -3
- package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
- package/dist/runtime/streams/stream-message-utils.js +0 -20
- package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
- package/dist/runtime/streams/stream-session-runtime.js +40 -85
- package/package.json +3 -3
- package/src/contracts/Configuration/Builders.ts +2 -0
- package/src/contracts/Configuration/InternalDefaults.ts +4 -0
- package/src/contracts/Configuration/RegistrationBuilders.ts +13 -0
- package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
- package/src/contracts/Configuration/RegistrationTypes.ts +3 -0
- package/src/runtime/actors/actor-mailbox.ts +7 -2
- package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
- package/src/runtime/actors/index.ts +77 -42
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
- package/src/runtime/execution/serial-execution-queue.ts +117 -29
- package/src/runtime/foundation/operation-registry.ts +16 -9
- package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
- package/src/runtime/foundation/service-stateful-registry.ts +10 -5
- package/src/runtime/foundation/service-stateful-runtime.ts +152 -224
- package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
- package/src/runtime/foundation/service-topology-registry.ts +30 -20
- package/src/runtime/handlers/handler-instance-scope.ts +2 -1
- package/src/runtime/host/actor-placement-coordinator.ts +8 -2
- package/src/runtime/host/actor-runtime-options-factory.ts +6 -0
- package/src/runtime/host/index.ts +6 -2
- package/src/runtime/host/instance-activation-authority.ts +58 -125
- package/src/runtime/host/spot-address-transport.ts +1 -1
- package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
- package/src/runtime/locations/domain-store-contract.ts +6 -1
- package/src/runtime/locations/in-memory-authority-store.ts +83 -12
- package/src/runtime/locations/in-memory-location-store.ts +22 -10
- package/src/runtime/locations/internal-location-contracts.ts +6 -0
- package/src/runtime/locations/internal-store-contracts.ts +1 -0
- package/src/runtime/locations/location-store-repository.ts +182 -69
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
- package/src/runtime/spots/spot-entry-activation.ts +4 -0
- package/src/runtime/spots/spot-node-runtime-manager.ts +7 -0
- package/src/runtime/spots/spot-serial-executor.ts +14 -5
- package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
- package/src/runtime/streams/index.ts +3 -1
- package/src/runtime/streams/protocol.ts +0 -13
- package/src/runtime/streams/session-context.ts +0 -32
- package/src/runtime/streams/session-serial-executor.ts +0 -8
- package/src/runtime/streams/stream-message-utils.ts +1 -27
- package/src/runtime/streams/stream-session-runtime.ts +54 -97
- package/dist/runtime/streams/session-requests.d.ts +0 -25
- package/dist/runtime/streams/session-requests.js +0 -101
- package/src/runtime/streams/session-requests.ts +0 -110
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ZlinkStreamContentType } from '@zlink-systems/stream-wire';
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
-
import { ZLinkFrameworkException } from '../../contracts';
|
|
3
|
+
import { ZLinkFrameworkErrorKind, ZLinkFrameworkException } from '../../contracts';
|
|
4
4
|
import type { ZLinkAuthoritySnapshot } from '../locations/internal-location-contracts';
|
|
5
5
|
import { decodeServiceClosingSpotAuthority } from './service-authority-payload-codec';
|
|
6
6
|
import {
|
|
@@ -280,9 +280,10 @@ export type ServiceInstanceAuthorityRead =
|
|
|
280
280
|
readonly authority?: ZLinkAuthoritySnapshot;
|
|
281
281
|
};
|
|
282
282
|
|
|
283
|
-
export type ServiceInstanceAuthorityReserve =
|
|
284
|
-
|
|
285
|
-
|
|
283
|
+
export type ServiceInstanceAuthorityReserve = {
|
|
284
|
+
readonly kind: 'reserved';
|
|
285
|
+
readonly reservation: ServiceInstanceActivationReservation;
|
|
286
|
+
};
|
|
286
287
|
|
|
287
288
|
/**
|
|
288
289
|
* Synchronous authority port used by the raw ingress turn. An asynchronous
|
|
@@ -337,13 +338,6 @@ export interface ServiceAsyncInstanceActivationAuthority {
|
|
|
337
338
|
): Promise<void>;
|
|
338
339
|
}
|
|
339
340
|
|
|
340
|
-
export class ServiceInstanceActivationRedirectError extends Error {
|
|
341
|
-
constructor(readonly route: ServiceInstanceRouteFence) {
|
|
342
|
-
super(`Instance Spot '${route.targetSpotId}' is owned by '${route.targetNodeRid}'.`);
|
|
343
|
-
this.name = 'ServiceInstanceActivationRedirectError';
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
341
|
export interface ServiceUserSpotOperationResult {
|
|
348
342
|
readonly terminalResult: number;
|
|
349
343
|
readonly failureCode: number;
|
|
@@ -438,6 +432,7 @@ export class ServiceStatefulRuntime {
|
|
|
438
432
|
Promise<{
|
|
439
433
|
readonly spot: Pick<ServiceSpotState, 'ref'>;
|
|
440
434
|
readonly route: ServiceInstanceRouteFence;
|
|
435
|
+
readonly stableType: string;
|
|
441
436
|
}>
|
|
442
437
|
>();
|
|
443
438
|
private closed = false;
|
|
@@ -980,7 +975,7 @@ export class ServiceStatefulRuntime {
|
|
|
980
975
|
const committed = await authority.commit(target, reservation, spot);
|
|
981
976
|
if (committed.kind !== 'committed') {
|
|
982
977
|
this.registry.closeSpot(spot.ref);
|
|
983
|
-
throw
|
|
978
|
+
throw activationRefused();
|
|
984
979
|
}
|
|
985
980
|
return this.finishRecoveredInstanceActivation(envelope, committed.route, expectedCurrentRoute);
|
|
986
981
|
}
|
|
@@ -1565,7 +1560,7 @@ export class ServiceStatefulRuntime {
|
|
|
1565
1560
|
this.nodeRid,
|
|
1566
1561
|
sourceSpotId,
|
|
1567
1562
|
'request',
|
|
1568
|
-
{ high:
|
|
1563
|
+
{ high: ZLINK_NATIVE_CORRELATION_OPERATION_NAMESPACE, low: pending.id },
|
|
1569
1564
|
deadlineUnixMs,
|
|
1570
1565
|
pending.id,
|
|
1571
1566
|
metadataFrame !== undefined
|
|
@@ -2069,6 +2064,12 @@ export class ServiceStatefulRuntime {
|
|
|
2069
2064
|
metadata ? record.parts[1] : undefined
|
|
2070
2065
|
);
|
|
2071
2066
|
}
|
|
2067
|
+
if (
|
|
2068
|
+
decoded.kind === 'instanceSpot' &&
|
|
2069
|
+
(decoded.operationKind === 'request') !== (record.requestSequence !== undefined)
|
|
2070
|
+
) {
|
|
2071
|
+
return 'protocolError';
|
|
2072
|
+
}
|
|
2072
2073
|
const hasPayload = [
|
|
2073
2074
|
'spotSend',
|
|
2074
2075
|
'spotRequest',
|
|
@@ -2131,6 +2132,14 @@ export class ServiceStatefulRuntime {
|
|
|
2131
2132
|
canonicalPayload
|
|
2132
2133
|
);
|
|
2133
2134
|
} catch (error) {
|
|
2135
|
+
if (
|
|
2136
|
+
decoded.kind === 'instanceSpot' &&
|
|
2137
|
+
decoded.activation === 'missing' &&
|
|
2138
|
+
(error instanceof ZLinkFrameworkException || error instanceof ServiceStaleGenerationError)
|
|
2139
|
+
) {
|
|
2140
|
+
this.finishMissingInstanceActivationFailure(record, decoded, error);
|
|
2141
|
+
return 'infrastructure';
|
|
2142
|
+
}
|
|
2134
2143
|
const correlation = statefulCorrelation(decoded);
|
|
2135
2144
|
if (correlation !== undefined) {
|
|
2136
2145
|
const result = failure(error);
|
|
@@ -2243,7 +2252,7 @@ export class ServiceStatefulRuntime {
|
|
|
2243
2252
|
sourceSpotId: record.sourceSpotId,
|
|
2244
2253
|
targetSpot: record.target.spot,
|
|
2245
2254
|
...(record.kind === 'spotRequest'
|
|
2246
|
-
? { reply: this.replyPort(ingress, record.correlation
|
|
2255
|
+
? { reply: this.replyPort(ingress, record.correlation) }
|
|
2247
2256
|
: {})
|
|
2248
2257
|
}
|
|
2249
2258
|
);
|
|
@@ -2291,7 +2300,7 @@ export class ServiceStatefulRuntime {
|
|
|
2291
2300
|
originalReplyRouteId: ingress.requestSequence ?? 0n
|
|
2292
2301
|
},
|
|
2293
2302
|
...(record.kind === 'actorRequest'
|
|
2294
|
-
? { reply: this.replyPort(ingress, record.correlation
|
|
2303
|
+
? { reply: this.replyPort(ingress, record.correlation) }
|
|
2295
2304
|
: {})
|
|
2296
2305
|
}
|
|
2297
2306
|
);
|
|
@@ -2344,7 +2353,8 @@ export class ServiceStatefulRuntime {
|
|
|
2344
2353
|
ingress: RawServiceIngressRecord,
|
|
2345
2354
|
record: Extract<ServiceStatefulWireRecord, { readonly kind: 'instanceSpot' }>,
|
|
2346
2355
|
payloadFrame: Buffer,
|
|
2347
|
-
metadataFrame?: Buffer
|
|
2356
|
+
metadataFrame?: Buffer,
|
|
2357
|
+
localReply?: NonNullable<ServiceStatefulMailboxData['reply']>
|
|
2348
2358
|
): RawServicePumpResult {
|
|
2349
2359
|
if (record.activation === 'missing' && this.asyncInstanceAuthority !== undefined) {
|
|
2350
2360
|
const ownedIngress = shareIngressOwnership(ingress);
|
|
@@ -2352,7 +2362,7 @@ export class ServiceStatefulRuntime {
|
|
|
2352
2362
|
ownedIngress,
|
|
2353
2363
|
record,
|
|
2354
2364
|
payloadFrame,
|
|
2355
|
-
|
|
2365
|
+
localReply,
|
|
2356
2366
|
metadataFrame
|
|
2357
2367
|
).finally(() => ownedIngress.applicationJobOwner?.close());
|
|
2358
2368
|
return 'infrastructure';
|
|
@@ -2384,7 +2394,7 @@ export class ServiceStatefulRuntime {
|
|
|
2384
2394
|
record,
|
|
2385
2395
|
payloadFrame,
|
|
2386
2396
|
spot,
|
|
2387
|
-
|
|
2397
|
+
localReply,
|
|
2388
2398
|
this.instanceApplicationTerminalCompletion(applicationTarget),
|
|
2389
2399
|
metadataFrame
|
|
2390
2400
|
);
|
|
@@ -2440,7 +2450,10 @@ export class ServiceStatefulRuntime {
|
|
|
2440
2450
|
}
|
|
2441
2451
|
const directReply =
|
|
2442
2452
|
record.operationKind === 'request'
|
|
2443
|
-
? (localReply ??
|
|
2453
|
+
? (localReply ??
|
|
2454
|
+
(ingress.requestSequence === undefined
|
|
2455
|
+
? undefined
|
|
2456
|
+
: this.replyPort(ingress, record.replyRouteId)))
|
|
2444
2457
|
: undefined;
|
|
2445
2458
|
const applicationTarget =
|
|
2446
2459
|
applicationTargetOverride ??
|
|
@@ -2475,7 +2488,7 @@ export class ServiceStatefulRuntime {
|
|
|
2475
2488
|
...(metadataFrame === undefined ? {} : { applicationMetadata: metadataFrame }),
|
|
2476
2489
|
...(terminalCompletion === undefined ? {} : { onTerminalCompletion: terminalCompletion }),
|
|
2477
2490
|
...(onHandlerTurnStarted === undefined ? {} : { onHandlerTurnStarted }),
|
|
2478
|
-
...(record.operationKind === 'request'
|
|
2491
|
+
...(record.operationKind === 'request' && directReply !== undefined
|
|
2479
2492
|
? {
|
|
2480
2493
|
reply:
|
|
2481
2494
|
onTerminalCompletion === undefined
|
|
@@ -2508,52 +2521,26 @@ export class ServiceStatefulRuntime {
|
|
|
2508
2521
|
): Promise<void> {
|
|
2509
2522
|
try {
|
|
2510
2523
|
this.validateInstanceIngress(ingress, record);
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
this.instanceApplicationTarget(record, activation.route.objectGeneration)
|
|
2528
|
-
);
|
|
2529
|
-
if (admitted !== 'application') {
|
|
2530
|
-
throw new Error('Activated Instance message was not admitted to the local queue.');
|
|
2531
|
-
}
|
|
2532
|
-
} catch (error) {
|
|
2533
|
-
let terminalError = error;
|
|
2534
|
-
if (error instanceof ServiceInstanceActivationRedirectError && !this.closed) {
|
|
2535
|
-
try {
|
|
2536
|
-
await this.relayMissingInstanceActivation(
|
|
2537
|
-
record,
|
|
2538
|
-
payloadFrame,
|
|
2539
|
-
error.route,
|
|
2540
|
-
ingress,
|
|
2541
|
-
localReply,
|
|
2542
|
-
metadataFrame
|
|
2543
|
-
);
|
|
2544
|
-
return;
|
|
2545
|
-
} catch (relayError) {
|
|
2546
|
-
terminalError = relayError;
|
|
2524
|
+
await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame, (activation) => {
|
|
2525
|
+
if (this.closed) return;
|
|
2526
|
+
const terminal = this.activationTerminalCompletion(record.target, activation.route);
|
|
2527
|
+
const admitted = this.enqueueActivatedInstanceSpot(
|
|
2528
|
+
ingress,
|
|
2529
|
+
record,
|
|
2530
|
+
payloadFrame,
|
|
2531
|
+
activation.spot,
|
|
2532
|
+
localReply,
|
|
2533
|
+
terminal.onTerminalCompletion,
|
|
2534
|
+
metadataFrame,
|
|
2535
|
+
terminal.onHandlerTurnStarted,
|
|
2536
|
+
this.instanceApplicationTarget(record, activation.route.objectGeneration)
|
|
2537
|
+
);
|
|
2538
|
+
if (admitted !== 'application') {
|
|
2539
|
+
throw new Error('Activated Instance message was not admitted to the local queue.');
|
|
2547
2540
|
}
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
if (localReply !== undefined) {
|
|
2552
|
-
localReply(result.terminalResult, result.failureCode);
|
|
2553
|
-
} else {
|
|
2554
|
-
if (record.replyRouteId === undefined) return;
|
|
2555
|
-
this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
|
|
2556
|
-
}
|
|
2541
|
+
});
|
|
2542
|
+
} catch (error) {
|
|
2543
|
+
this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
|
|
2557
2544
|
}
|
|
2558
2545
|
}
|
|
2559
2546
|
|
|
@@ -2604,7 +2591,12 @@ export class ServiceStatefulRuntime {
|
|
|
2604
2591
|
throw new Error('Rematerialized Instance message was not admitted to the local queue.');
|
|
2605
2592
|
}
|
|
2606
2593
|
} catch (error) {
|
|
2607
|
-
if (
|
|
2594
|
+
if (
|
|
2595
|
+
record.operationKind !== 'request' ||
|
|
2596
|
+
record.replyRouteId === undefined ||
|
|
2597
|
+
ingress.requestSequence === undefined
|
|
2598
|
+
)
|
|
2599
|
+
return;
|
|
2608
2600
|
const result = failure(error);
|
|
2609
2601
|
this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
|
|
2610
2602
|
}
|
|
@@ -2664,10 +2656,11 @@ export class ServiceStatefulRuntime {
|
|
|
2664
2656
|
|
|
2665
2657
|
private instanceApplicationTerminalCompletion(
|
|
2666
2658
|
target: ServiceInstanceApplicationTarget
|
|
2667
|
-
): () => Promise<void> {
|
|
2659
|
+
): (() => Promise<void>) | undefined {
|
|
2660
|
+
const lifecycle = this.instanceApplicationLifecycle;
|
|
2661
|
+
if (lifecycle === undefined) return undefined;
|
|
2668
2662
|
return async () => {
|
|
2669
|
-
const authorityReleased =
|
|
2670
|
-
(await this.instanceApplicationLifecycle?.completeTerminal(target)) ?? false;
|
|
2663
|
+
const authorityReleased = await lifecycle.completeTerminal(target);
|
|
2671
2664
|
if (!authorityReleased) return;
|
|
2672
2665
|
const current = this.instanceIntents.get(target.targetSpotId);
|
|
2673
2666
|
if (
|
|
@@ -2731,91 +2724,39 @@ export class ServiceStatefulRuntime {
|
|
|
2731
2724
|
for (const waiter of waiters) waiter();
|
|
2732
2725
|
}
|
|
2733
2726
|
|
|
2734
|
-
private
|
|
2727
|
+
private finishMissingInstanceActivationFailure(
|
|
2728
|
+
ingress: RawServiceIngressRecord,
|
|
2735
2729
|
record: Extract<
|
|
2736
2730
|
ServiceStatefulWireRecord,
|
|
2737
2731
|
{ readonly kind: 'instanceSpot'; readonly activation: 'missing' }
|
|
2738
2732
|
>,
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
const redirectedTarget: ServiceInstanceActivationTarget = {
|
|
2750
|
-
...record.target,
|
|
2751
|
-
targetNodeRid: route.targetNodeRid,
|
|
2752
|
-
targetNodeGeneration: route.targetNodeGeneration,
|
|
2753
|
-
targetSpotId: route.targetSpotId
|
|
2754
|
-
};
|
|
2755
|
-
if (record.operationKind === 'send') {
|
|
2756
|
-
const submitted = await this.submitOneWay(
|
|
2757
|
-
route.targetNodeRid,
|
|
2758
|
-
instanceOperationParts(
|
|
2759
|
-
[
|
|
2760
|
-
encodeInstanceSpotActivationHeader(
|
|
2761
|
-
redirectedTarget,
|
|
2762
|
-
this.nodeGeneration,
|
|
2763
|
-
this.nodeRid,
|
|
2764
|
-
record.sourceSpotId,
|
|
2765
|
-
'send',
|
|
2766
|
-
record.operation,
|
|
2767
|
-
record.deadlineUnixMs,
|
|
2768
|
-
undefined,
|
|
2769
|
-
metadataFrame !== undefined
|
|
2770
|
-
),
|
|
2771
|
-
payloadFrame
|
|
2772
|
-
],
|
|
2773
|
-
metadataFrame
|
|
2774
|
-
)
|
|
2775
|
-
);
|
|
2776
|
-
if (submitted !== SubmitResult.Ok) {
|
|
2777
|
-
throw new Error(`Instance activation redirect was not admitted: ${submitted}.`);
|
|
2733
|
+
error: unknown,
|
|
2734
|
+
localReply?: NonNullable<ServiceStatefulMailboxData['reply']>
|
|
2735
|
+
): void {
|
|
2736
|
+
if (record.operationKind === 'request') {
|
|
2737
|
+
if (record.deadlineUnixMs <= BigInt(Date.now())) return;
|
|
2738
|
+
const result = failure(error);
|
|
2739
|
+
if (localReply !== undefined) {
|
|
2740
|
+
localReply(result.terminalResult, result.failureCode);
|
|
2741
|
+
} else if (ingress.requestSequence !== undefined && record.replyRouteId !== undefined) {
|
|
2742
|
+
this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
|
|
2778
2743
|
}
|
|
2779
2744
|
return;
|
|
2780
2745
|
}
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
record.deadlineUnixMs,
|
|
2796
|
-
pending.id,
|
|
2797
|
-
metadataFrame !== undefined
|
|
2798
|
-
),
|
|
2799
|
-
payloadFrame
|
|
2800
|
-
],
|
|
2801
|
-
metadataFrame
|
|
2802
|
-
),
|
|
2803
|
-
remainingMs,
|
|
2804
|
-
'instanceSpotRequest'
|
|
2805
|
-
);
|
|
2806
|
-
const result = await pending.promise;
|
|
2807
|
-
if (localReply !== undefined) {
|
|
2808
|
-
localReply(result.terminalResult, result.failureCode, result.payload);
|
|
2809
|
-
return;
|
|
2810
|
-
}
|
|
2811
|
-
if (record.replyRouteId !== undefined) {
|
|
2812
|
-
this.replyWire(
|
|
2813
|
-
ingress,
|
|
2814
|
-
record.replyRouteId,
|
|
2815
|
-
result.terminalResult,
|
|
2816
|
-
result.failureCode,
|
|
2817
|
-
result.payload
|
|
2818
|
-
);
|
|
2746
|
+
const reporter = this.dispatchErrors;
|
|
2747
|
+
if (reporter?.captureEnabled() === true) {
|
|
2748
|
+
reporter.report({
|
|
2749
|
+
surface: ZLinkDispatchErrorSurface.InstanceSpot,
|
|
2750
|
+
messageKind: ZLinkDispatchMessageKind.Send,
|
|
2751
|
+
reason: ZLinkDispatchErrorReason.StaleTarget,
|
|
2752
|
+
action: ZLinkDispatchErrorAction.Drop,
|
|
2753
|
+
meshName: this.dispatchErrorMeshName,
|
|
2754
|
+
sourceRid: record.sourceNodeRid,
|
|
2755
|
+
spotId: record.target.targetSpotId,
|
|
2756
|
+
instanceSpotType: record.target.stableType,
|
|
2757
|
+
activationState: 'activating',
|
|
2758
|
+
error
|
|
2759
|
+
});
|
|
2819
2760
|
}
|
|
2820
2761
|
}
|
|
2821
2762
|
|
|
@@ -2971,13 +2912,10 @@ export class ServiceStatefulRuntime {
|
|
|
2971
2912
|
return local;
|
|
2972
2913
|
}
|
|
2973
2914
|
if (current.kind === 'ready') {
|
|
2974
|
-
throw
|
|
2915
|
+
throw activationRefused();
|
|
2975
2916
|
}
|
|
2976
2917
|
|
|
2977
2918
|
const reserved = authority.reserve(target, record.operation, record.deadlineUnixMs);
|
|
2978
|
-
if (reserved.kind === 'ready') {
|
|
2979
|
-
throw new ServiceInstanceActivationRedirectError(reserved.route);
|
|
2980
|
-
}
|
|
2981
2919
|
|
|
2982
2920
|
let activation: { readonly spot: ServiceSpotState; readonly created: boolean };
|
|
2983
2921
|
try {
|
|
@@ -2994,7 +2932,7 @@ export class ServiceStatefulRuntime {
|
|
|
2994
2932
|
const committed = authority.commit(target, reserved.reservation, activation.spot);
|
|
2995
2933
|
if (committed.kind === 'lost') {
|
|
2996
2934
|
if (activation.created) this.registry.closeSpot(activation.spot.ref);
|
|
2997
|
-
throw
|
|
2935
|
+
throw activationRefused();
|
|
2998
2936
|
}
|
|
2999
2937
|
if (!routeMatchesLocal(committed.route, activation.spot, this.nodeRid, this.nodeGeneration)) {
|
|
3000
2938
|
if (activation.created) this.registry.closeSpot(activation.spot.ref);
|
|
@@ -3009,15 +2947,35 @@ export class ServiceStatefulRuntime {
|
|
|
3009
2947
|
{ readonly kind: 'instanceSpot'; readonly activation: 'missing' }
|
|
3010
2948
|
>,
|
|
3011
2949
|
payloadFrame: Buffer,
|
|
3012
|
-
metadataFrame
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
2950
|
+
metadataFrame: Buffer | undefined,
|
|
2951
|
+
admit: (activation: {
|
|
2952
|
+
readonly spot: Pick<ServiceSpotState, 'ref'>;
|
|
2953
|
+
readonly route: ServiceInstanceRouteFence;
|
|
2954
|
+
}) => void
|
|
2955
|
+
): Promise<unknown> {
|
|
2956
|
+
const key = record.target.targetSpotId;
|
|
3018
2957
|
const pending = this.pendingInstanceActivations.get(key);
|
|
3019
|
-
|
|
3020
|
-
|
|
2958
|
+
const ready =
|
|
2959
|
+
pending ??
|
|
2960
|
+
this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
|
|
2961
|
+
...activation,
|
|
2962
|
+
stableType: record.target.stableType
|
|
2963
|
+
}));
|
|
2964
|
+
const operation = ready.then((value) => {
|
|
2965
|
+
if (value.stableType !== record.target.stableType) {
|
|
2966
|
+
throw new ZLinkFrameworkException(
|
|
2967
|
+
ZLinkFrameworkErrorKind.TypeMismatch,
|
|
2968
|
+
'Instance Spot type does not match.'
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2971
|
+
admit(value);
|
|
2972
|
+
return value;
|
|
2973
|
+
});
|
|
2974
|
+
// The caller owns operation failure. Keep the activation available for later participants.
|
|
2975
|
+
const activation = operation.then(
|
|
2976
|
+
(value) => value,
|
|
2977
|
+
() => ready
|
|
2978
|
+
);
|
|
3021
2979
|
this.pendingInstanceActivations.set(key, activation);
|
|
3022
2980
|
const clear = () => {
|
|
3023
2981
|
if (this.pendingInstanceActivations.get(key) === activation) {
|
|
@@ -3025,7 +2983,7 @@ export class ServiceStatefulRuntime {
|
|
|
3025
2983
|
}
|
|
3026
2984
|
};
|
|
3027
2985
|
void activation.then(clear, clear);
|
|
3028
|
-
return
|
|
2986
|
+
return operation;
|
|
3029
2987
|
}
|
|
3030
2988
|
|
|
3031
2989
|
private async runMissingInstanceActivation(
|
|
@@ -3073,14 +3031,14 @@ export class ServiceStatefulRuntime {
|
|
|
3073
3031
|
}
|
|
3074
3032
|
};
|
|
3075
3033
|
}
|
|
3034
|
+
if (current.kind === 'ready' && current.route.targetNodeRid !== this.nodeRid) {
|
|
3035
|
+
throw activationRefused();
|
|
3036
|
+
}
|
|
3076
3037
|
if (
|
|
3077
3038
|
current.kind === 'ready' &&
|
|
3078
3039
|
current.authority !== undefined &&
|
|
3079
3040
|
decodeServiceClosingSpotAuthority(current.authority.payload) !== undefined
|
|
3080
3041
|
) {
|
|
3081
|
-
if (current.route.targetNodeRid !== this.nodeRid) {
|
|
3082
|
-
throw new ServiceInstanceActivationRedirectError(current.route);
|
|
3083
|
-
}
|
|
3084
3042
|
if (
|
|
3085
3043
|
current.route.targetNodeGeneration !== this.nodeGeneration ||
|
|
3086
3044
|
(local !== undefined &&
|
|
@@ -3095,17 +3053,11 @@ export class ServiceStatefulRuntime {
|
|
|
3095
3053
|
};
|
|
3096
3054
|
}
|
|
3097
3055
|
if (!this.acceptsLocalMissingInstancePlacement()) {
|
|
3098
|
-
if (current.kind === 'ready' && current.route.targetNodeRid !== this.nodeRid) {
|
|
3099
|
-
throw new ServiceInstanceActivationRedirectError(current.route);
|
|
3100
|
-
}
|
|
3101
3056
|
// A draining or retiring node must not create a successor generation
|
|
3102
3057
|
// from a stale client route while relocation is publishing its target.
|
|
3103
3058
|
throw new ServiceStaleGenerationError('spot', target.targetSpotId);
|
|
3104
3059
|
}
|
|
3105
3060
|
if (current.kind === 'ready') {
|
|
3106
|
-
if (current.route.targetNodeRid !== this.nodeRid) {
|
|
3107
|
-
throw new ServiceInstanceActivationRedirectError(current.route);
|
|
3108
|
-
}
|
|
3109
3061
|
if (current.route.targetNodeGeneration !== this.nodeGeneration) {
|
|
3110
3062
|
throw new ServiceStaleGenerationError('spot', target.targetSpotId);
|
|
3111
3063
|
}
|
|
@@ -3148,16 +3100,9 @@ export class ServiceStatefulRuntime {
|
|
|
3148
3100
|
await lifecycle.discard(target);
|
|
3149
3101
|
materialized = lifecycle.isMaterialized(target);
|
|
3150
3102
|
}
|
|
3151
|
-
// The
|
|
3152
|
-
//
|
|
3153
|
-
//
|
|
3154
|
-
// A newer reservation can also become visible after an explicit close
|
|
3155
|
-
// releases authority but before local disposal removes the old
|
|
3156
|
-
// projection. The close gate serializes that replacement; rejecting it
|
|
3157
|
-
// here turns a valid next-generation Instance intent into a stale-route
|
|
3158
|
-
// failure.
|
|
3159
|
-
// If disposal did not remove an orphaned materialization, keep fencing
|
|
3160
|
-
// it instead of admitting work into two application generations.
|
|
3103
|
+
// The local projection does not decide reservation ownership. Preserve
|
|
3104
|
+
// the Creating fence for Reserve, which refuses a competing attempt.
|
|
3105
|
+
// Keep fencing an orphan that disposal did not remove.
|
|
3161
3106
|
if (
|
|
3162
3107
|
(!joinsCreating && current.kind !== 'missing') ||
|
|
3163
3108
|
(!joinsCreating && materialized !== false && !materializing)
|
|
@@ -3186,9 +3131,6 @@ export class ServiceStatefulRuntime {
|
|
|
3186
3131
|
} finally {
|
|
3187
3132
|
deadline.close();
|
|
3188
3133
|
}
|
|
3189
|
-
if (reserved.kind === 'ready') {
|
|
3190
|
-
throw new ServiceInstanceActivationRedirectError(reserved.route);
|
|
3191
|
-
}
|
|
3192
3134
|
if (this.closed || record.deadlineUnixMs < BigInt(Date.now())) {
|
|
3193
3135
|
await authority.abort(target, reserved.reservation);
|
|
3194
3136
|
throw new ServiceStaleGenerationError('spot', target.targetSpotId);
|
|
@@ -3224,7 +3166,7 @@ export class ServiceStatefulRuntime {
|
|
|
3224
3166
|
if (committed.kind === 'lost') {
|
|
3225
3167
|
if (activation.created) this.registry.closeSpot(activation.spot.ref);
|
|
3226
3168
|
await this.instanceApplicationLifecycle?.discard(target);
|
|
3227
|
-
throw
|
|
3169
|
+
throw activationRefused();
|
|
3228
3170
|
}
|
|
3229
3171
|
if (!routeMatchesLocal(committed.route, activation.spot, this.nodeRid, this.nodeGeneration)) {
|
|
3230
3172
|
if (activation.created) this.registry.closeSpot(activation.spot.ref);
|
|
@@ -3269,7 +3211,7 @@ export class ServiceStatefulRuntime {
|
|
|
3269
3211
|
const confirmed = await authority.read(target);
|
|
3270
3212
|
if (confirmed.kind !== 'ready' || !sameInstanceRoute(confirmed.route, route)) {
|
|
3271
3213
|
if (confirmed.kind === 'ready' && confirmed.route.targetNodeRid !== this.nodeRid) {
|
|
3272
|
-
throw
|
|
3214
|
+
throw activationRefused();
|
|
3273
3215
|
}
|
|
3274
3216
|
throw new ServiceStaleGenerationError('spot', target.targetSpotId);
|
|
3275
3217
|
}
|
|
@@ -3404,7 +3346,7 @@ export class ServiceStatefulRuntime {
|
|
|
3404
3346
|
kindData: control,
|
|
3405
3347
|
...(canonicalApplicationPayload === undefined ? {} : { canonicalApplicationPayload }),
|
|
3406
3348
|
reply: (terminalResult, failureCode, replyPayload, tail) => {
|
|
3407
|
-
return this.replyPort(ingress, record.correlation
|
|
3349
|
+
return this.replyPort(ingress, record.correlation)(
|
|
3408
3350
|
terminalResult,
|
|
3409
3351
|
failureCode,
|
|
3410
3352
|
replyPayload,
|
|
@@ -4037,14 +3979,12 @@ export class ServiceStatefulRuntime {
|
|
|
4037
3979
|
|
|
4038
3980
|
private replyPort(
|
|
4039
3981
|
ingress: RawServiceIngressRecord,
|
|
4040
|
-
correlation: bigint | undefined
|
|
4041
|
-
operationKind: 'spotRequest' | 'actorRequest' | 'actorJoin' | 'instanceSpotRequest'
|
|
3982
|
+
correlation: bigint | undefined
|
|
4042
3983
|
): NonNullable<ServiceStatefulMailboxData['reply']> {
|
|
4043
3984
|
if (correlation === undefined)
|
|
4044
3985
|
throw new ServiceWireProtocolError('Request correlation is missing.');
|
|
4045
3986
|
return (terminalResult, failureCode, payload, tail) => {
|
|
4046
3987
|
this.replyWire(ingress, correlation, terminalResult, failureCode, payload, tail);
|
|
4047
|
-
void operationKind;
|
|
4048
3988
|
return true;
|
|
4049
3989
|
};
|
|
4050
3990
|
}
|
|
@@ -4057,10 +3997,11 @@ export class ServiceStatefulRuntime {
|
|
|
4057
3997
|
payload?: ServiceApplicationPayload,
|
|
4058
3998
|
tail?: ServiceStatefulReplyTail
|
|
4059
3999
|
): void {
|
|
4060
|
-
|
|
4000
|
+
const parts = [
|
|
4061
4001
|
encodeStatefulReply(correlation, terminalResult, failureCode, tail),
|
|
4062
4002
|
...(payload === undefined ? [] : [encodeApplicationPayload(payload)])
|
|
4063
|
-
]
|
|
4003
|
+
];
|
|
4004
|
+
this.raw.replyService(ingress, parts);
|
|
4064
4005
|
}
|
|
4065
4006
|
|
|
4066
4007
|
private handleUserSpotOperation(
|
|
@@ -4763,23 +4704,17 @@ export class ServiceStatefulRuntime {
|
|
|
4763
4704
|
return;
|
|
4764
4705
|
}
|
|
4765
4706
|
if (decoded.kind === 'instanceSpot') {
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
targetSpot: activation.ref,
|
|
4778
|
-
reply: localReply
|
|
4779
|
-
});
|
|
4780
|
-
} catch (error) {
|
|
4781
|
-
this.operations.reply(pending.id, failure(error));
|
|
4782
|
-
}
|
|
4707
|
+
const hasMetadata = (ingress.flags & M6bServiceWireFlag.metadata) !== 0;
|
|
4708
|
+
const metadataFrame = hasMetadata
|
|
4709
|
+
? validateServiceMetadataFrame(ingress.parts[1]!)
|
|
4710
|
+
: undefined;
|
|
4711
|
+
this.enqueueInstanceSpot(
|
|
4712
|
+
ingress,
|
|
4713
|
+
decoded,
|
|
4714
|
+
ingress.parts[hasMetadata ? 2 : 1]!,
|
|
4715
|
+
metadataFrame,
|
|
4716
|
+
localReply
|
|
4717
|
+
);
|
|
4783
4718
|
return;
|
|
4784
4719
|
}
|
|
4785
4720
|
this.operations.reply(pending.id, {
|
|
@@ -4790,7 +4725,7 @@ export class ServiceStatefulRuntime {
|
|
|
4790
4725
|
}
|
|
4791
4726
|
|
|
4792
4727
|
private completeRemoteReply(
|
|
4793
|
-
pending: ServiceStatefulPendingOperation,
|
|
4728
|
+
pending: Pick<ServiceStatefulPendingOperation, 'id'>,
|
|
4794
4729
|
targetNodeRid: string,
|
|
4795
4730
|
operationKind:
|
|
4796
4731
|
| 'spotRequest'
|
|
@@ -5038,9 +4973,7 @@ export class ServiceStatefulRuntime {
|
|
|
5038
4973
|
...(wire.correlation === undefined ? {} : { correlation: wire.correlation }),
|
|
5039
4974
|
sourceSpotId: wire.sourceSpotId,
|
|
5040
4975
|
targetSpot: wire.target.spot,
|
|
5041
|
-
...(wire.kind === 'spotRequest'
|
|
5042
|
-
? { reply: this.replyPort(ingress, wire.correlation, 'spotRequest') }
|
|
5043
|
-
: {})
|
|
4976
|
+
...(wire.kind === 'spotRequest' ? { reply: this.replyPort(ingress, wire.correlation) } : {})
|
|
5044
4977
|
}
|
|
5045
4978
|
);
|
|
5046
4979
|
}
|
|
@@ -5523,6 +5456,13 @@ function actorLocation(actor: ServiceActorState): ActorLocation {
|
|
|
5523
5456
|
};
|
|
5524
5457
|
}
|
|
5525
5458
|
|
|
5459
|
+
function activationRefused(): ZLinkFrameworkException {
|
|
5460
|
+
return new ZLinkFrameworkException(
|
|
5461
|
+
ZLinkFrameworkErrorKind.Unavailable,
|
|
5462
|
+
'Instance cold activation authority was refused.'
|
|
5463
|
+
);
|
|
5464
|
+
}
|
|
5465
|
+
|
|
5526
5466
|
function failure(error: unknown): ServiceStatefulResult {
|
|
5527
5467
|
if (error instanceof ServiceStaleGenerationError) {
|
|
5528
5468
|
return internalFrameworkWireReply(ZLinkFrameworkInternalErrorKind.ActorLocationStale);
|
|
@@ -5895,18 +5835,6 @@ function instanceOperationKey(
|
|
|
5895
5835
|
);
|
|
5896
5836
|
}
|
|
5897
5837
|
|
|
5898
|
-
function instanceActivationKey(
|
|
5899
|
-
record: Extract<
|
|
5900
|
-
ServiceStatefulWireRecord,
|
|
5901
|
-
{ readonly kind: 'instanceSpot'; readonly activation: 'missing' }
|
|
5902
|
-
>
|
|
5903
|
-
): string {
|
|
5904
|
-
return (
|
|
5905
|
-
`${record.target.targetNodeRid}\0${record.target.targetNodeGeneration}\0` +
|
|
5906
|
-
`${record.target.targetSpotId}\0${record.target.stableType}`
|
|
5907
|
-
);
|
|
5908
|
-
}
|
|
5909
|
-
|
|
5910
5838
|
function subscriptionKey(channelName: string, topicFilter: string): string {
|
|
5911
5839
|
if (channelName.length === 0 || topicFilter.length === 0) {
|
|
5912
5840
|
throw new TypeError('Channel name and topic filter must be non-empty.');
|