@zlink-systems/framework 0.29.0 → 0.30.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.
Files changed (168) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +1 -2
  3. package/dist/contracts/Configuration/Configs.d.ts +0 -5
  4. package/dist/contracts/Configuration/RegistrationBuilders.js +6 -4
  5. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -6
  6. package/dist/contracts/Configuration/RegistrationValidators.js +4 -4
  7. package/dist/contracts/Configuration/index.d.ts +1 -1
  8. package/dist/internal.d.ts +1 -0
  9. package/dist/internal.js +3 -1
  10. package/dist/runtime/actors/actor-handoff.d.ts +3 -2
  11. package/dist/runtime/actors/actor-handoff.js +9 -4
  12. package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
  13. package/dist/runtime/actors/index.d.ts +2 -0
  14. package/dist/runtime/actors/index.js +21 -6
  15. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +3 -1
  16. package/dist/runtime/application-jobs/application-ingress-record-owner.js +6 -1
  17. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +3 -1
  18. package/dist/runtime/application-jobs/application-job-queue-scope.js +5 -0
  19. package/dist/runtime/application-jobs/contracts.d.ts +5 -1
  20. package/dist/runtime/backend/contracts/index.d.ts +10 -19
  21. package/dist/runtime/backend/mesh-completion-table.d.ts +6 -4
  22. package/dist/runtime/backend/mesh-completion-table.js +28 -18
  23. package/dist/runtime/backend/mesh-dispatch-pump.js +21 -25
  24. package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
  25. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
  26. package/dist/runtime/backend/node/node-backend-adapter-support.js +24 -29
  27. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -9
  28. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  29. package/dist/runtime/backend/node/node-raw-binding-port.js +11 -6
  30. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +10 -12
  31. package/dist/runtime/backend/node/node-raw-mesh-backend.js +64 -98
  32. package/dist/runtime/backend/node/node-socket-backend-adapter.js +10 -5
  33. package/dist/runtime/backend/raw-binding-port.d.ts +9 -2
  34. package/dist/runtime/channels/channel-receive-loops.js +3 -3
  35. package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
  36. package/dist/runtime/channels/channel-socket-registry.d.ts +10 -3
  37. package/dist/runtime/channels/channel-socket-registry.js +59 -82
  38. package/dist/runtime/diagnostics/message-flow.d.ts +1 -0
  39. package/dist/runtime/diagnostics/message-flow.js +3 -3
  40. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +15 -1
  41. package/dist/runtime/foundation/raw-service-mesh-runtime.js +193 -64
  42. package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
  43. package/dist/runtime/foundation/service-liveness-registry.js +57 -44
  44. package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
  45. package/dist/runtime/foundation/service-mailbox.js +2 -1
  46. package/dist/runtime/foundation/service-stateful-runtime.d.ts +7 -6
  47. package/dist/runtime/foundation/service-stateful-runtime.js +60 -41
  48. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -2
  49. package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
  50. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -3
  51. package/dist/runtime/foundation/service-topology-registry.js +9 -4
  52. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  53. package/dist/runtime/host/actor-packet-relay.js +7 -7
  54. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  55. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  56. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  57. package/dist/runtime/host/actor-runtime-options-factory.js +2 -1
  58. package/dist/runtime/host/application-job-queue.d.ts +11 -4
  59. package/dist/runtime/host/application-job-queue.js +66 -10
  60. package/dist/runtime/host/entry-actor-runtime.js +1 -1
  61. package/dist/runtime/host/index.d.ts +1 -1
  62. package/dist/runtime/host/index.js +18 -13
  63. package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
  64. package/dist/runtime/host/instance-activation-authority.js +89 -25
  65. package/dist/runtime/host/service-relocation-host-runtime.js +2 -2
  66. package/dist/runtime/host/spot-address-transport.d.ts +1 -0
  67. package/dist/runtime/host/spot-address-transport.js +25 -12
  68. package/dist/runtime/host/spot-runtime-options-factory.js +2 -2
  69. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
  70. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
  71. package/dist/runtime/locations/domain-store-contract.d.ts +3 -2
  72. package/dist/runtime/locations/in-memory-authority-store.d.ts +5 -2
  73. package/dist/runtime/locations/in-memory-authority-store.js +107 -14
  74. package/dist/runtime/locations/in-memory-location-store.d.ts +3 -3
  75. package/dist/runtime/locations/in-memory-location-store.js +31 -13
  76. package/dist/runtime/locations/internal-location-contracts.d.ts +8 -0
  77. package/dist/runtime/locations/location-store-repository.d.ts +4 -2
  78. package/dist/runtime/locations/location-store-repository.js +209 -33
  79. package/dist/runtime/locations/resolvers.d.ts +2 -1
  80. package/dist/runtime/locations/resolvers.js +10 -1
  81. package/dist/runtime/messaging/submission-result.d.ts +1 -1
  82. package/dist/runtime/messaging/submission-result.js +2 -2
  83. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +2 -0
  84. package/dist/runtime/spots/index.d.ts +4 -3
  85. package/dist/runtime/spots/index.js +14 -14
  86. package/dist/runtime/spots/spot-activation.d.ts +2 -1
  87. package/dist/runtime/spots/spot-activation.js +4 -4
  88. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
  89. package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
  90. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
  91. package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
  92. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  93. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
  94. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  95. package/dist/runtime/spots/spot-entry-activation.js +6 -1
  96. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +3 -1
  97. package/dist/runtime/spots/spot-node-runtime-manager.js +10 -11
  98. package/dist/runtime/spots/spot-routing-internal.d.ts +6 -0
  99. package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -1
  100. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  101. package/dist/runtime/spots/spot-serial-executor.js +10 -1
  102. package/dist/runtime/streams/stream-session-runtime.js +1 -1
  103. package/package.json +3 -3
  104. package/src/contracts/Configuration/Builders.ts +1 -2
  105. package/src/contracts/Configuration/Configs.ts +0 -6
  106. package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
  107. package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
  108. package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
  109. package/src/contracts/Configuration/index.ts +1 -6
  110. package/src/internal.ts +1 -0
  111. package/src/runtime/actors/actor-handoff.ts +13 -4
  112. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  113. package/src/runtime/actors/index.ts +77 -42
  114. package/src/runtime/application-jobs/application-ingress-record-owner.ts +12 -2
  115. package/src/runtime/application-jobs/application-job-queue-scope.ts +6 -1
  116. package/src/runtime/application-jobs/contracts.ts +14 -1
  117. package/src/runtime/backend/contracts/index.ts +10 -18
  118. package/src/runtime/backend/mesh-completion-table.ts +36 -19
  119. package/src/runtime/backend/mesh-dispatch-pump.ts +19 -22
  120. package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
  121. package/src/runtime/backend/node/node-backend-adapter-support.ts +31 -29
  122. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
  123. package/src/runtime/backend/node/node-raw-binding-port.ts +19 -10
  124. package/src/runtime/backend/node/node-raw-mesh-backend.ts +81 -111
  125. package/src/runtime/backend/node/node-socket-backend-adapter.ts +12 -8
  126. package/src/runtime/backend/raw-binding-port.ts +12 -2
  127. package/src/runtime/channels/channel-receive-loops.ts +3 -3
  128. package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
  129. package/src/runtime/channels/channel-socket-registry.ts +84 -130
  130. package/src/runtime/diagnostics/message-flow.ts +1 -1
  131. package/src/runtime/foundation/raw-service-mesh-runtime.ts +268 -70
  132. package/src/runtime/foundation/service-liveness-registry.ts +68 -59
  133. package/src/runtime/foundation/service-mailbox.ts +7 -1
  134. package/src/runtime/foundation/service-stateful-runtime.ts +109 -77
  135. package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -79
  136. package/src/runtime/foundation/service-topology-registry.ts +13 -7
  137. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  138. package/src/runtime/host/actor-packet-relay.ts +7 -0
  139. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  140. package/src/runtime/host/actor-runtime-options-factory.ts +7 -0
  141. package/src/runtime/host/application-job-queue.ts +77 -10
  142. package/src/runtime/host/entry-actor-runtime.ts +1 -0
  143. package/src/runtime/host/index.ts +30 -7
  144. package/src/runtime/host/instance-activation-authority.ts +132 -27
  145. package/src/runtime/host/service-relocation-host-runtime.ts +2 -0
  146. package/src/runtime/host/spot-address-transport.ts +45 -22
  147. package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
  148. package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
  149. package/src/runtime/locations/domain-store-contract.ts +6 -2
  150. package/src/runtime/locations/in-memory-authority-store.ts +153 -12
  151. package/src/runtime/locations/in-memory-location-store.ts +39 -13
  152. package/src/runtime/locations/internal-location-contracts.ts +10 -0
  153. package/src/runtime/locations/location-store-repository.ts +295 -36
  154. package/src/runtime/locations/resolvers.ts +25 -2
  155. package/src/runtime/messaging/submission-result.ts +2 -2
  156. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +2 -0
  157. package/src/runtime/spots/index.ts +16 -0
  158. package/src/runtime/spots/spot-activation.ts +5 -0
  159. package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
  160. package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
  161. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  162. package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
  163. package/src/runtime/spots/spot-entry-activation.ts +7 -0
  164. package/src/runtime/spots/spot-node-runtime-manager.ts +14 -12
  165. package/src/runtime/spots/spot-routing-internal.ts +14 -0
  166. package/src/runtime/spots/spot-runtime-ports.ts +2 -0
  167. package/src/runtime/spots/spot-serial-executor.ts +10 -4
  168. package/src/runtime/streams/stream-session-runtime.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  import { UINT64_MAX } from '@zlink-systems/stream-wire';
2
2
  export interface ServiceProbe {
3
3
  readonly nodeRoutingId: string;
4
- readonly connectionId: string;
4
+ readonly connectionId: string | bigint;
5
5
  readonly probeId: bigint;
6
6
  }
7
7
 
@@ -10,20 +10,52 @@ export interface ServiceLivenessTick {
10
10
  readonly timedOutNodes: readonly string[];
11
11
  }
12
12
 
13
- interface PeerState {
14
- readonly connectionId: string;
13
+ export const DEFAULT_SERVICE_PROBE_INTERVAL_MS = 5_000;
14
+ export const DEFAULT_SERVICE_PEER_TIMEOUT_MS = 15_000;
15
+
16
+ export class ServiceLivenessConnection {
15
17
  deadlineMs: number;
16
18
  nextProbeMs: number;
17
19
  outstandingProbe?: bigint;
18
- ready: boolean;
19
- }
20
20
 
21
- export const DEFAULT_SERVICE_PROBE_INTERVAL_MS = 5_000;
22
- export const DEFAULT_SERVICE_PEER_TIMEOUT_MS = 15_000;
21
+ constructor(
22
+ readonly connectionId: string | bigint,
23
+ nowMs: number,
24
+ private readonly probeIntervalMs = DEFAULT_SERVICE_PROBE_INTERVAL_MS,
25
+ private readonly peerTimeoutMs = DEFAULT_SERVICE_PEER_TIMEOUT_MS
26
+ ) {
27
+ this.deadlineMs = nowMs + peerTimeoutMs;
28
+ this.nextProbeMs = nowMs;
29
+ }
30
+
31
+ readonly recordReceived = (nowMs = performance.now()): void => {
32
+ this.deadlineMs = Math.max(this.deadlineMs, nowMs + this.peerTimeoutMs);
33
+ };
34
+
35
+ isExpired(nowMs: number): boolean {
36
+ return nowMs >= this.deadlineMs;
37
+ }
38
+
39
+ tryGetProbe(nowMs: number, allocateProbeId: () => bigint): bigint | undefined {
40
+ if (nowMs < this.nextProbeMs) return undefined;
41
+ this.outstandingProbe ??= allocateProbeId();
42
+ do {
43
+ this.nextProbeMs += this.probeIntervalMs;
44
+ } while (this.nextProbeMs <= nowMs);
45
+ return this.outstandingProbe;
46
+ }
47
+
48
+ acknowledge(probeId: bigint, nowMs: number): boolean {
49
+ this.recordReceived(nowMs);
50
+ if (this.outstandingProbe !== probeId) return false;
51
+ this.outstandingProbe = undefined;
52
+ return true;
53
+ }
54
+ }
23
55
 
24
56
  /** Uses monotonic milliseconds and never treats application traffic as a probe ACK. */
25
57
  export class ServiceLivenessRegistry {
26
- private readonly peers = new Map<string, PeerState>();
58
+ private readonly peers = new Map<string, ServiceLivenessConnection>();
27
59
  private nextProbeId = 1n;
28
60
 
29
61
  constructor(
@@ -40,33 +72,31 @@ export class ServiceLivenessRegistry {
40
72
  }
41
73
  }
42
74
 
43
- admit(nodeRoutingId: string, connectionId: string, nowMs: number): void {
75
+ admit(
76
+ nodeRoutingId: string,
77
+ connectionId: string | bigint,
78
+ nowMs: number
79
+ ): ServiceLivenessConnection {
44
80
  requireIdentity(nodeRoutingId, 'nodeRoutingId');
45
- requireIdentity(connectionId, 'connectionId');
81
+ if (typeof connectionId === 'string') requireIdentity(connectionId, 'connectionId');
82
+ else if (connectionId === 0n) throw new TypeError('connectionId must be nonzero.');
46
83
  const current = this.peers.get(nodeRoutingId);
47
- if (current?.connectionId === connectionId) return;
48
- this.peers.set(nodeRoutingId, {
84
+ if (current?.connectionId === connectionId) return current;
85
+ const admitted = new ServiceLivenessConnection(
49
86
  connectionId,
50
- deadlineMs: nowMs + this.peerTimeoutMs,
51
- nextProbeMs: nowMs + this.probeIntervalMs,
52
- ready: false
53
- });
87
+ nowMs,
88
+ this.probeIntervalMs,
89
+ this.peerTimeoutMs
90
+ );
91
+ this.peers.set(nodeRoutingId, admitted);
92
+ return admitted;
54
93
  }
55
94
 
56
- requestProbe(nodeRoutingId: string, connectionId: string, nowMs: number): boolean {
57
- const current = this.peers.get(nodeRoutingId);
58
- if (
59
- current === undefined ||
60
- current.connectionId !== connectionId ||
61
- current.ready ||
62
- current.outstandingProbe !== undefined
63
- )
64
- return false;
65
- current.nextProbeMs = Math.min(current.nextProbeMs, nowMs);
66
- return true;
95
+ recordReceived(connection: ServiceLivenessConnection, nowMs: number): void {
96
+ connection.recordReceived(nowMs);
67
97
  }
68
98
 
69
- disconnect(nodeRoutingId: string, connectionId: string): boolean {
99
+ disconnect(nodeRoutingId: string, connectionId: string | bigint): boolean {
70
100
  const current = this.peers.get(nodeRoutingId);
71
101
  if (current === undefined || current.connectionId !== connectionId) return false;
72
102
  this.peers.delete(nodeRoutingId);
@@ -75,27 +105,18 @@ export class ServiceLivenessRegistry {
75
105
 
76
106
  acknowledge(
77
107
  nodeRoutingId: string,
78
- connectionId: string,
108
+ connectionId: string | bigint,
79
109
  probeId: bigint,
80
110
  nowMs: number
81
111
  ): boolean {
82
112
  const current = this.peers.get(nodeRoutingId);
83
- if (
84
- current === undefined ||
85
- current.connectionId !== connectionId ||
86
- current.outstandingProbe !== probeId
87
- ) {
88
- return false;
89
- }
90
- current.outstandingProbe = undefined;
91
- current.deadlineMs = nowMs + this.peerTimeoutMs;
92
- current.ready = true;
93
- return true;
113
+ if (current === undefined || current.connectionId !== connectionId) return false;
114
+ return current.acknowledge(probeId, nowMs);
94
115
  }
95
116
 
96
117
  acknowledgeProbe(
97
118
  nodeRoutingId: string,
98
- connectionId: string,
119
+ connectionId: string | bigint,
99
120
  probeId: bigint
100
121
  ): ServiceProbe | undefined {
101
122
  const current = this.peers.get(nodeRoutingId);
@@ -109,21 +130,14 @@ export class ServiceLivenessRegistry {
109
130
  const probes: ServiceProbe[] = [];
110
131
  const timedOutNodes: string[] = [];
111
132
  for (const [nodeRoutingId, peer] of this.peers) {
112
- if (peer.deadlineMs <= nowMs) {
133
+ if (peer.isExpired(nowMs)) {
113
134
  timedOutNodes.push(nodeRoutingId);
114
135
  this.peers.delete(nodeRoutingId);
115
136
  continue;
116
137
  }
117
- if (peer.nextProbeMs > nowMs) continue;
118
- peer.outstandingProbe ??= this.allocateProbeId();
119
- probes.push({
120
- nodeRoutingId,
121
- connectionId: peer.connectionId,
122
- probeId: peer.outstandingProbe
123
- });
124
- do {
125
- peer.nextProbeMs += this.probeIntervalMs;
126
- } while (peer.nextProbeMs <= nowMs);
138
+ const probeId = peer.tryGetProbe(nowMs, this.allocateProbeId);
139
+ if (probeId !== undefined)
140
+ probes.push({ nodeRoutingId, connectionId: peer.connectionId, probeId });
127
141
  }
128
142
  timedOutNodes.sort();
129
143
  return { probes, timedOutNodes };
@@ -133,16 +147,11 @@ export class ServiceLivenessRegistry {
133
147
  return this.peers.size;
134
148
  }
135
149
 
136
- isReady(nodeRoutingId: string, connectionId: string): boolean {
137
- const current = this.peers.get(nodeRoutingId);
138
- return current?.connectionId === connectionId && current.ready;
139
- }
140
-
141
- private allocateProbeId(): bigint {
150
+ private readonly allocateProbeId = (): bigint => {
142
151
  const result = this.nextProbeId++;
143
152
  if (this.nextProbeId > UINT64_MAX) this.nextProbeId = 1n;
144
153
  return result;
145
- }
154
+ };
146
155
  }
147
156
 
148
157
  function requireIdentity(value: string, field: string): void {
@@ -1,4 +1,5 @@
1
1
  import type { ApplicationJobRecordLease } from '../application-jobs/application-ingress-record-owner';
2
+ import type { ReceiveRecord } from './service-runtime-contracts';
2
3
 
3
4
  export type ServiceMailboxDomain = 'application' | 'infrastructure';
4
5
 
@@ -23,6 +24,8 @@ export interface ServiceMailboxRecord {
23
24
  readonly stateful?: unknown;
24
25
  /** Host queue permit plus its ordinary Framework-owned ingress record. */
25
26
  readonly applicationJob?: ApplicationJobRecordLease;
27
+ /** Unregistered raw-pull terminal, retained by the existing mailbox owner. */
28
+ readonly completion?: ReceiveRecord;
26
29
  }
27
30
 
28
31
  export interface ServiceMailboxClaim {
@@ -85,7 +88,10 @@ export class ServiceMailbox {
85
88
  }
86
89
 
87
90
  tryEnqueue(record: ServiceMailboxRecord): boolean {
88
- if (record.owner.length === 0 || record.parts.length === 0) {
91
+ if (
92
+ record.owner.length === 0 ||
93
+ (record.parts.length === 0 && record.completion === undefined)
94
+ ) {
89
95
  throw new TypeError('Mailbox records require an owner and retained payload.');
90
96
  }
91
97
  const target = this.domain(record.domain);
@@ -90,6 +90,7 @@ import {
90
90
  type ServiceBoundSessionActorAuthority,
91
91
  type ServiceDirectSpotRouteFence,
92
92
  type ServiceInstanceActivationTarget,
93
+ type ServiceInstanceColdActivationTarget,
93
94
  type ServiceInstanceRouteFence,
94
95
  type ServiceMessageFollowRoute,
95
96
  type ServiceRetiredBoundSessionRouteFence,
@@ -313,7 +314,12 @@ export interface ServiceInstanceActivationAuthority {
313
314
  * operation completes; the binding callback itself never blocks.
314
315
  */
315
316
  export interface ServiceAsyncInstanceActivationAuthority {
316
- read(target: ServiceInstanceActivationTarget): Promise<ServiceInstanceAuthorityRead>;
317
+ read(
318
+ target: ServiceInstanceActivationTarget,
319
+ instanceIntent?: true,
320
+ activation?: ServiceInstanceActivationRecoveryEnvelope,
321
+ acceptsLocal?: (route: ServiceInstanceRouteFence) => boolean
322
+ ): Promise<ServiceInstanceAuthorityRead>;
317
323
  reserve(
318
324
  activation: Omit<ServiceInstanceActivationRecoveryEnvelope, 'targetMeshName'>,
319
325
  signal?: AbortSignal
@@ -432,6 +438,7 @@ export class ServiceStatefulRuntime {
432
438
  Promise<{
433
439
  readonly spot: Pick<ServiceSpotState, 'ref'>;
434
440
  readonly route: ServiceInstanceRouteFence;
441
+ readonly stableType: string;
435
442
  }>
436
443
  >();
437
444
  private closed = false;
@@ -882,7 +889,7 @@ export class ServiceStatefulRuntime {
882
889
  const record = {
883
890
  kind: 'instanceSpot' as const,
884
891
  activation: 'missing' as const,
885
- target,
892
+ target: { ...target, targetMeshName: envelope.targetMeshName },
886
893
  sourceNodeGeneration: envelope.sourceNodeGeneration,
887
894
  sourceNodeRid: envelope.sourceNodeRid,
888
895
  ...(envelope.sourceSpotId === undefined ? {} : { sourceSpotId: envelope.sourceSpotId }),
@@ -901,7 +908,7 @@ export class ServiceStatefulRuntime {
901
908
  sourceRoutingId: envelope.sourceNodeRid,
902
909
  parts: [
903
910
  encodeInstanceSpotActivationHeader(
904
- target,
911
+ record.target,
905
912
  envelope.sourceNodeGeneration,
906
913
  envelope.sourceNodeRid,
907
914
  envelope.sourceSpotId,
@@ -1441,7 +1448,7 @@ export class ServiceStatefulRuntime {
1441
1448
  }
1442
1449
 
1443
1450
  async sendToMissingInstanceSpot(
1444
- target: ServiceInstanceActivationTarget,
1451
+ target: ServiceInstanceColdActivationTarget,
1445
1452
  payload: ServiceApplicationPayload,
1446
1453
  deadlineUnixMs: bigint,
1447
1454
  sourceSpotId?: string,
@@ -1457,7 +1464,7 @@ export class ServiceStatefulRuntime {
1457
1464
  }
1458
1465
 
1459
1466
  async sendToMissingInstanceSpotFrame(
1460
- target: ServiceInstanceActivationTarget,
1467
+ target: ServiceInstanceColdActivationTarget,
1461
1468
  payloadFrame: Buffer,
1462
1469
  deadlineUnixMs: bigint,
1463
1470
  sourceSpotId?: string,
@@ -1522,7 +1529,7 @@ export class ServiceStatefulRuntime {
1522
1529
  }
1523
1530
 
1524
1531
  requestToMissingInstanceSpot(
1525
- target: ServiceInstanceActivationTarget,
1532
+ target: ServiceInstanceColdActivationTarget,
1526
1533
  payload: ServiceApplicationPayload,
1527
1534
  timeoutMs: number,
1528
1535
  sourceSpotId?: string,
@@ -1540,7 +1547,7 @@ export class ServiceStatefulRuntime {
1540
1547
  }
1541
1548
 
1542
1549
  requestToMissingInstanceSpotFrame(
1543
- target: ServiceInstanceActivationTarget,
1550
+ target: ServiceInstanceColdActivationTarget,
1544
1551
  payloadFrame: Buffer,
1545
1552
  deadlineUnixMs: bigint,
1546
1553
  sourceSpotId?: string,
@@ -1559,7 +1566,7 @@ export class ServiceStatefulRuntime {
1559
1566
  this.nodeRid,
1560
1567
  sourceSpotId,
1561
1568
  'request',
1562
- { high: this.nodeGeneration, low: pending.id },
1569
+ { high: ZLINK_NATIVE_CORRELATION_OPERATION_NAMESPACE, low: pending.id },
1563
1570
  deadlineUnixMs,
1564
1571
  pending.id,
1565
1572
  metadataFrame !== undefined
@@ -2520,27 +2527,24 @@ export class ServiceStatefulRuntime {
2520
2527
  ): Promise<void> {
2521
2528
  try {
2522
2529
  this.validateInstanceIngress(ingress, record);
2523
- const activation = await this.activateMissingInstanceAsync(
2524
- record,
2525
- payloadFrame,
2526
- metadataFrame
2527
- );
2528
- if (this.closed) return;
2529
- const terminal = this.activationTerminalCompletion(record.target, activation.route);
2530
- const admitted = this.enqueueActivatedInstanceSpot(
2531
- ingress,
2532
- record,
2533
- payloadFrame,
2534
- activation.spot,
2535
- localReply,
2536
- terminal.onTerminalCompletion,
2537
- metadataFrame,
2538
- terminal.onHandlerTurnStarted,
2539
- this.instanceApplicationTarget(record, activation.route.objectGeneration)
2540
- );
2541
- if (admitted !== 'application') {
2542
- throw new Error('Activated Instance message was not admitted to the local queue.');
2543
- }
2530
+ await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame, (activation) => {
2531
+ if (this.closed) return;
2532
+ const terminal = this.activationTerminalCompletion(record.target, activation.route);
2533
+ const admitted = this.enqueueActivatedInstanceSpot(
2534
+ ingress,
2535
+ record,
2536
+ payloadFrame,
2537
+ activation.spot,
2538
+ localReply,
2539
+ terminal.onTerminalCompletion,
2540
+ metadataFrame,
2541
+ terminal.onHandlerTurnStarted,
2542
+ this.instanceApplicationTarget(record, activation.route.objectGeneration)
2543
+ );
2544
+ if (admitted !== 'application') {
2545
+ throw new Error('Activated Instance message was not admitted to the local queue.');
2546
+ }
2547
+ });
2544
2548
  } catch (error) {
2545
2549
  this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
2546
2550
  }
@@ -2943,21 +2947,72 @@ export class ServiceStatefulRuntime {
2943
2947
  return activation.spot;
2944
2948
  }
2945
2949
 
2950
+ private localReadyInstance(
2951
+ target: ServiceInstanceActivationTarget,
2952
+ route: ServiceInstanceRouteFence
2953
+ ): ServiceSpotState | undefined {
2954
+ const local = this.registry.spot(target.targetSpotId);
2955
+ if (
2956
+ local !== undefined &&
2957
+ (local.kind !== 'instance' || local.stableType !== target.stableType)
2958
+ ) {
2959
+ throw new ServiceStaleGenerationError('spot', target.targetSpotId);
2960
+ }
2961
+ return local !== undefined &&
2962
+ local.state === 'ready' &&
2963
+ routeMatchesLocal(route, local, this.nodeRid, this.nodeGeneration)
2964
+ ? local
2965
+ : undefined;
2966
+ }
2967
+
2946
2968
  private activateMissingInstanceAsync(
2947
2969
  record: Extract<
2948
2970
  ServiceStatefulWireRecord,
2949
2971
  { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
2950
2972
  >,
2951
2973
  payloadFrame: Buffer,
2952
- metadataFrame?: Buffer
2953
- ): Promise<{
2954
- readonly spot: Pick<ServiceSpotState, 'ref'>;
2955
- readonly route: ServiceInstanceRouteFence;
2956
- }> {
2957
- const key = instanceActivationKey(record);
2974
+ metadataFrame: Buffer | undefined,
2975
+ admit: (activation: {
2976
+ readonly spot: Pick<ServiceSpotState, 'ref'>;
2977
+ readonly route: ServiceInstanceRouteFence;
2978
+ }) => void
2979
+ ): Promise<unknown> {
2980
+ const key = record.target.targetSpotId;
2958
2981
  const pending = this.pendingInstanceActivations.get(key);
2959
- if (pending !== undefined) return pending;
2960
- const activation = this.runMissingInstanceActivation(record, payloadFrame, metadataFrame);
2982
+ const ready =
2983
+ pending ??
2984
+ this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
2985
+ ...activation,
2986
+ stableType: record.target.stableType
2987
+ }));
2988
+ const operation = ready.then(async (value) => {
2989
+ if (pending !== undefined) {
2990
+ await this.asyncInstanceAuthority?.read(
2991
+ record.target,
2992
+ undefined,
2993
+ {
2994
+ ...record,
2995
+ targetMeshName: record.target.targetMeshName,
2996
+ ...(metadataFrame === undefined ? {} : { metadataFrame }),
2997
+ applicationPayloadFrame: payloadFrame
2998
+ },
2999
+ (route) => this.localReadyInstance(record.target, route) !== undefined
3000
+ );
3001
+ }
3002
+ if (value.stableType !== record.target.stableType) {
3003
+ throw new ZLinkFrameworkException(
3004
+ ZLinkFrameworkErrorKind.TypeMismatch,
3005
+ 'Instance Spot type does not match.'
3006
+ );
3007
+ }
3008
+ admit(value);
3009
+ return value;
3010
+ });
3011
+ // The caller owns operation failure. Keep the activation available for later participants.
3012
+ const activation = operation.then(
3013
+ (value) => value,
3014
+ () => ready
3015
+ );
2961
3016
  this.pendingInstanceActivations.set(key, activation);
2962
3017
  const clear = () => {
2963
3018
  if (this.pendingInstanceActivations.get(key) === activation) {
@@ -2965,7 +3020,7 @@ export class ServiceStatefulRuntime {
2965
3020
  }
2966
3021
  };
2967
3022
  void activation.then(clear, clear);
2968
- return activation;
3023
+ return operation;
2969
3024
  }
2970
3025
 
2971
3026
  private async runMissingInstanceActivation(
@@ -2992,8 +3047,19 @@ export class ServiceStatefulRuntime {
2992
3047
  throw new Error('Async Instance activation authority is not registered.');
2993
3048
  }
2994
3049
 
3050
+ const activationRequest = {
3051
+ ...record,
3052
+ targetMeshName: record.target.targetMeshName,
3053
+ ...(metadataFrame === undefined ? {} : { metadataFrame }),
3054
+ applicationPayloadFrame: payloadFrame
3055
+ };
3056
+ const current = await authority.read(
3057
+ target,
3058
+ true,
3059
+ activationRequest,
3060
+ (route) => this.localReadyInstance(target, route) !== undefined
3061
+ );
2995
3062
  const local = this.registry.spot(target.targetSpotId);
2996
- const current = await authority.read(target);
2997
3063
  if (
2998
3064
  current.kind === 'creating' &&
2999
3065
  current.authority?.allocation.state === 'active' &&
@@ -3043,17 +3109,9 @@ export class ServiceStatefulRuntime {
3043
3109
  if (current.route.targetNodeGeneration !== this.nodeGeneration) {
3044
3110
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3045
3111
  }
3046
- if (
3047
- local !== undefined &&
3048
- (local.kind !== 'instance' || local.stableType !== target.stableType)
3049
- ) {
3050
- throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3051
- }
3052
- if (
3053
- local !== undefined &&
3054
- routeMatchesLocal(current.route, local, this.nodeRid, this.nodeGeneration)
3055
- ) {
3056
- return { spot: local, route: current.route };
3112
+ const readyLocal = this.localReadyInstance(target, current.route);
3113
+ if (readyLocal !== undefined) {
3114
+ return { spot: readyLocal, route: current.route };
3057
3115
  }
3058
3116
  return await this.reconcileReadyInstanceProjection(target, current.route, authority);
3059
3117
  }
@@ -3095,21 +3153,7 @@ export class ServiceStatefulRuntime {
3095
3153
  const deadline = operationDeadline(record.deadlineUnixMs);
3096
3154
  let reserved: ServiceInstanceAuthorityReserve;
3097
3155
  try {
3098
- reserved = await authority.reserve(
3099
- {
3100
- target,
3101
- sourceNodeRid: record.sourceNodeRid,
3102
- sourceNodeGeneration: record.sourceNodeGeneration,
3103
- ...(record.sourceSpotId === undefined ? {} : { sourceSpotId: record.sourceSpotId }),
3104
- operationKind: record.operationKind,
3105
- operation: record.operation,
3106
- ...(record.replyRouteId === undefined ? {} : { replyRouteId: record.replyRouteId }),
3107
- deadlineUnixMs: record.deadlineUnixMs,
3108
- ...(metadataFrame === undefined ? {} : { metadataFrame }),
3109
- applicationPayloadFrame: payloadFrame
3110
- },
3111
- deadline.signal
3112
- );
3156
+ reserved = await authority.reserve(activationRequest, deadline.signal);
3113
3157
  } finally {
3114
3158
  deadline.close();
3115
3159
  }
@@ -5817,18 +5861,6 @@ function instanceOperationKey(
5817
5861
  );
5818
5862
  }
5819
5863
 
5820
- function instanceActivationKey(
5821
- record: Extract<
5822
- ServiceStatefulWireRecord,
5823
- { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
5824
- >
5825
- ): string {
5826
- return (
5827
- `${record.target.targetNodeRid}\0${record.target.targetNodeGeneration}\0` +
5828
- `${record.target.targetSpotId}\0${record.target.stableType}`
5829
- );
5830
- }
5831
-
5832
5864
  function subscriptionKey(channelName: string, topicFilter: string): string {
5833
5865
  if (channelName.length === 0 || topicFilter.length === 0) {
5834
5866
  throw new TypeError('Channel name and topic filter must be non-empty.');