@zlink-systems/framework 0.19.0 → 0.20.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 (70) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +0 -2
  3. package/dist/contracts/Configuration/Configs.d.ts +1 -0
  4. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -2
  5. package/dist/contracts/Locations/Authority.d.ts +1 -7
  6. package/dist/runtime/actors/actor-authority-publication.js +25 -22
  7. package/dist/runtime/actors/actor-creation.js +30 -9
  8. package/dist/runtime/actors/actor-runtime-state.d.ts +3 -0
  9. package/dist/runtime/actors/index.d.ts +3 -0
  10. package/dist/runtime/actors/index.js +14 -3
  11. package/dist/runtime/admission.d.ts +2 -0
  12. package/dist/runtime/admission.js +11 -5
  13. package/dist/runtime/backend/contracts/index.d.ts +4 -1
  14. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -0
  15. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  16. package/dist/runtime/backend/node/node-raw-binding-port.d.ts +2 -1
  17. package/dist/runtime/backend/node/node-raw-binding-port.js +12 -5
  18. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +1 -1
  19. package/dist/runtime/backend/node/node-socket-backend-adapter.js +6 -0
  20. package/dist/runtime/channels/channel-socket-options.js +7 -0
  21. package/dist/runtime/channels/channel-socket-registry.js +3 -0
  22. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -1
  23. package/dist/runtime/foundation/service-stateful-runtime.js +1 -5
  24. package/dist/runtime/host/actor-placement-coordinator.d.ts +14 -5
  25. package/dist/runtime/host/actor-placement-coordinator.js +159 -47
  26. package/dist/runtime/host/index.d.ts +2 -1
  27. package/dist/runtime/host/index.js +77 -120
  28. package/dist/runtime/host/location-runtime-owner.js +4 -2
  29. package/dist/runtime/host/remote-actor-join-receiver.js +12 -3
  30. package/dist/runtime/locations/creation-operation-id.d.ts +4 -0
  31. package/dist/runtime/locations/creation-operation-id.js +12 -0
  32. package/dist/runtime/locations/in-memory-authority-store.js +8 -22
  33. package/dist/runtime/locations/location-store-repository.js +36 -59
  34. package/dist/runtime/locations/opaque-record-key.d.ts +5 -0
  35. package/dist/runtime/locations/opaque-record-key.js +16 -0
  36. package/dist/runtime/locations/runtime.d.ts +7 -0
  37. package/dist/runtime/locations/runtime.js +130 -42
  38. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +1672 -0
  39. package/dist/runtime/protocol/service_wire_codec.generated.js +9169 -0
  40. package/dist/runtime/spots/spot-node-runtime-manager.js +2 -2
  41. package/package.json +2 -2
  42. package/src/contracts/Configuration/Builders.ts +0 -2
  43. package/src/contracts/Configuration/Configs.ts +1 -0
  44. package/src/contracts/Configuration/RegistrationTypes.ts +1 -2
  45. package/src/contracts/Locations/Authority.ts +1 -8
  46. package/src/runtime/actors/actor-authority-publication.ts +25 -22
  47. package/src/runtime/actors/actor-creation.ts +45 -11
  48. package/src/runtime/actors/actor-runtime-state.ts +4 -0
  49. package/src/runtime/actors/index.ts +32 -4
  50. package/src/runtime/admission.ts +8 -5
  51. package/src/runtime/backend/contracts/index.ts +4 -1
  52. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +2 -1
  53. package/src/runtime/backend/node/node-raw-binding-port.ts +16 -5
  54. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -1
  55. package/src/runtime/backend/node/node-socket-backend-adapter.ts +7 -0
  56. package/src/runtime/channels/channel-socket-options.ts +9 -0
  57. package/src/runtime/channels/channel-socket-registry.ts +4 -0
  58. package/src/runtime/foundation/service-stateful-runtime.ts +2 -6
  59. package/src/runtime/host/actor-placement-coordinator.ts +216 -77
  60. package/src/runtime/host/index.ts +95 -135
  61. package/src/runtime/host/location-runtime-owner.ts +4 -2
  62. package/src/runtime/host/remote-actor-join-receiver.ts +15 -3
  63. package/src/runtime/locations/creation-operation-id.ts +11 -0
  64. package/src/runtime/locations/in-memory-authority-store.ts +7 -29
  65. package/src/runtime/locations/location-store-repository.ts +71 -69
  66. package/src/runtime/locations/opaque-record-key.ts +26 -0
  67. package/src/runtime/locations/runtime.ts +154 -60
  68. package/src/runtime/protocol/service_wire_codec.generated.ts +8302 -0
  69. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  70. package/src/runtime/spots/spot-node-runtime-manager.ts +2 -2
@@ -137,6 +137,7 @@ class ZLinkSpotNodeRuntimeManager {
137
137
  const node = meshAdapter.createMeshNode(this.options.context, {
138
138
  meshName: spotNodeName,
139
139
  routingId,
140
+ receiveTimeoutMs: spotNode.router?.receiveTimeoutMs,
140
141
  applicationJobQueue: this.applicationJobQueue,
141
142
  applicationJobReceiveFlowFailureSink: this.options.applicationJobReceiveFlowFailureSink,
142
143
  peerAdmissionSealed: () => this.options.peerAdmissionSealed?.(spotNodeName) ?? false
@@ -399,8 +400,7 @@ class ZLinkSpotNodeRuntimeManager {
399
400
  await this.publishMeshNodeState(state, signal, meshName);
400
401
  }
401
402
  entrySpotId(meshName, registration) {
402
- if (effectiveObjectRole(registration) !== contracts_1.ZLinkObjectRole.Server
403
- || registration.entrySpotType === undefined) {
403
+ if (effectiveObjectRole(registration) !== contracts_1.ZLinkObjectRole.Server) {
404
404
  return undefined;
405
405
  }
406
406
  let entrySpotId = this.entrySpotIds.get(meshName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zlink-systems/framework",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "@opentelemetry/api": "^1.9.1",
21
21
  "@opentelemetry/api-logs": "^0.221.0",
22
- "@zlink-systems/stream-wire": "0.19.0",
22
+ "@zlink-systems/stream-wire": "0.20.0",
23
23
  "@zlink-systems/zlink": "1.2.1"
24
24
  }
25
25
  }
@@ -103,8 +103,6 @@ export interface ZLinkMeshChannelServerBuilder {
103
103
  export interface ZLinkMeshNodeSocketConfig {
104
104
  sendHighWaterMark: number;
105
105
  receiveHighWaterMark: number;
106
- mailboxMessageBudget: number;
107
- mailboxByteBudget: number;
108
106
  receiveTimeoutMs?: number;
109
107
  sendTimeoutMs?: number;
110
108
  }
@@ -7,6 +7,7 @@ export interface ZLinkSocketConfig {
7
7
  weight?: number;
8
8
  sendHighWaterMark?: number;
9
9
  receiveHighWaterMark?: number;
10
+ receiveTimeoutMs?: number;
10
11
  sendTimeoutMs?: number;
11
12
  }
12
13
 
@@ -198,8 +198,6 @@ export interface ZLinkClientCapabilityOptions {
198
198
  readonly manualConnections?: readonly string[];
199
199
  readonly sendHighWaterMark?: number;
200
200
  readonly receiveHighWaterMark?: number;
201
- readonly mailboxMessageBudget?: number;
202
- readonly mailboxByteBudget?: number;
203
201
  readonly sendTimeoutMs?: number;
204
202
  readonly maxMessageSize?: number;
205
203
  }
@@ -434,6 +432,7 @@ export interface ZLinkSpotRouterCapabilityOptions {
434
432
  readonly routingId?: string;
435
433
  readonly sendHighWaterMark?: number;
436
434
  readonly receiveHighWaterMark?: number;
435
+ readonly receiveTimeoutMs?: number;
437
436
  readonly sendTimeoutMs?: number;
438
437
  }
439
438
 
@@ -154,12 +154,9 @@ export interface ZLinkCreationOperationIdentity {
154
154
  readonly operationId: ZLinkCreationOperationId;
155
155
  }
156
156
 
157
- export type ZLinkCreationTerminalState = 'created' | 'rejected' | 'failed';
158
-
159
157
  export interface ZLinkCreationTerminalPublication {
160
158
  readonly operation: ZLinkCreationOperationIdentity;
161
159
  readonly terminalEnvelope: Uint8Array;
162
- readonly terminalEnvelopeSha256: Uint8Array;
163
160
  /**
164
161
  * The original operation deadline. Providers retain the terminal for five
165
162
  * minutes after this absolute instant.
@@ -168,19 +165,15 @@ export interface ZLinkCreationTerminalPublication {
168
165
  }
169
166
 
170
167
  export interface ZLinkCreationTerminalRecord {
171
- readonly state: ZLinkCreationTerminalState;
172
168
  readonly operation: ZLinkCreationOperationIdentity;
173
- readonly reservationId: string;
174
- readonly objectKind: ZLinkPlacementObjectKind;
175
169
  readonly terminalEnvelope: Uint8Array;
176
- readonly terminalEnvelopeSha256: Uint8Array;
177
170
  readonly expiresAt: Date;
178
171
  readonly storeNow: Date;
179
172
  }
180
173
 
181
174
  export type ZLinkCreationTerminalReadResult =
182
175
  | { readonly kind: 'missing'; readonly storeNow: Date }
183
- | { readonly kind: 'found'; readonly record: ZLinkCreationTerminalRecord };
176
+ | { readonly kind: 'found'; readonly terminalEnvelope: Uint8Array };
184
177
 
185
178
  export interface ZLinkObjectReserveRequest {
186
179
  readonly key: ZLinkObjectCreationKey;
@@ -1,4 +1,4 @@
1
- import { createHash, randomBytes } from 'node:crypto';
1
+ import { createHash } from 'node:crypto';
2
2
  import type {
3
3
  ActorRef
4
4
  } from '../../contracts';
@@ -6,6 +6,7 @@ import { ZLinkSpotKind } from '../../contracts';
6
6
  import type { ZLinkLocationOwnerToken } from '../../contracts/Locations';
7
7
  import type { ZLinkAuthoritySnapshot } from '../locations/internal-location-contracts';
8
8
  import type { ZLinkObjectCreationStore } from '../locations/internal-store-contracts';
9
+ import { randomOperationId } from '../locations/creation-operation-id';
9
10
  import {
10
11
  actorRelocationAuthorityApplicationPayload,
11
12
  decodeActorAuthorityPayload,
@@ -18,6 +19,7 @@ import {
18
19
  replaceServiceRelocationAuthorityApplicationPayload,
19
20
  serviceRelocationAuthorityApplicationPayload
20
21
  } from '../foundation/service-relocation-runtime';
22
+ import { encodeCreationOperationTerminalV1 } from '../protocol/service_wire_codec.generated';
21
23
 
22
24
  const CREATION_OPERATION_TIMEOUT_MS = 30_000;
23
25
 
@@ -74,12 +76,20 @@ export async function publishInitialActorAuthority(
74
76
  }
75
77
 
76
78
  try {
77
- const terminalEnvelope = Buffer.from(JSON.stringify({
78
- status: 'created',
79
- actorId: identity.actor.actorId,
80
- actorGeneration: identity.actor.objectGeneration.toString()
81
- }), 'utf8');
82
- const operationBytes = randomBytes(16);
79
+ const terminalEnvelope = Buffer.from(encodeCreationOperationTerminalV1({
80
+ terminalResult: 'ok',
81
+ failureCode: 'none',
82
+ hasCreation: 'true',
83
+ creation: {
84
+ createResult: 'created',
85
+ actor: {
86
+ actorId: identity.actor.actorId,
87
+ objectGeneration: identity.actor.objectGeneration
88
+ }
89
+ },
90
+ hasApplicationPayload: 'false'
91
+ }, { runtimePredicates: {} }));
92
+ const operationId = randomOperationId();
83
93
  const completed = await store.completeCreation({
84
94
  key: { kind: 'actor', globalId: identity.actor.actorId },
85
95
  reservationId: reserved.reservationId,
@@ -92,13 +102,9 @@ export async function publishInitialActorAuthority(
92
102
  operation: {
93
103
  sourceNodeRid: identity.actor.nodeRid,
94
104
  sourceNodeGeneration: identity.ownerNodeGeneration,
95
- operationId: {
96
- high: operationBytes.readBigUInt64BE(0),
97
- low: operationBytes.readBigUInt64BE(8)
98
- }
105
+ operationId
99
106
  },
100
107
  terminalEnvelope,
101
- terminalEnvelopeSha256: createHash('sha256').update(terminalEnvelope).digest(),
102
108
  operationDeadline: new Date(Date.now() + CREATION_OPERATION_TIMEOUT_MS)
103
109
  }
104
110
  }
@@ -111,16 +117,13 @@ export async function publishInitialActorAuthority(
111
117
  requireActorAuthority(completed.ready, identity);
112
118
  return completed.ready;
113
119
  } catch (error) {
114
- try {
115
- await store.abort({
116
- key: { kind: 'actor', globalId: identity.actor.actorId },
117
- reservationId: reserved.reservationId,
118
- expectedStoreVersion: reserved.creating.storeVersion.value,
119
- target
120
- }, signal);
121
- } catch {
122
- // A completed creation cannot be aborted; the validated authority remains.
123
- }
120
+ const aborted = await store.abort({
121
+ key: { kind: 'actor', globalId: identity.actor.actorId },
122
+ reservationId: reserved.reservationId,
123
+ expectedStoreVersion: reserved.creating.storeVersion.value,
124
+ target
125
+ }, signal);
126
+ if (aborted.kind === 'stale') throw error;
124
127
  throw error;
125
128
  }
126
129
  }
@@ -3,6 +3,7 @@ import type {
3
3
  RoutingId,
4
4
  Type,
5
5
  ZLinkActor,
6
+ ZLinkActorCreateResponse,
6
7
  ZLinkActorFactory,
7
8
  } from '../../contracts';
8
9
  import type { ZLinkProviderResolver } from '../../contracts/Common/ZLinkProviderResolver';
@@ -53,10 +54,17 @@ export class ZLinkActorCreationCoordinator {
53
54
  this.options.actorDestroyedCleanup?.(actorId);
54
55
  state.clearAfterDestroy();
55
56
  },
56
- () => this.createActorAfterClaim(actorId, actorType, state, createRequest, true, signal)
57
+ () => this.createActorAfterClaim(
58
+ actorId,
59
+ actorType,
60
+ state,
61
+ createRequest,
62
+ true,
63
+ signal
64
+ )
57
65
  );
58
66
  if (activation.activated !== undefined) {
59
- if (activation.activated.status === 'rejected') {
67
+ if (activation.activated.status !== 'created') {
60
68
  await lifecycle.releaseActor(actorType, actorId);
61
69
  return activation.activated;
62
70
  }
@@ -77,7 +85,14 @@ export class ZLinkActorCreationCoordinator {
77
85
  );
78
86
  }
79
87
 
80
- return await this.createActorAfterClaim(actorId, actorType, state, createRequest, claimLocation, signal);
88
+ return await this.createActorAfterClaim(
89
+ actorId,
90
+ actorType,
91
+ state,
92
+ createRequest,
93
+ claimLocation,
94
+ signal
95
+ );
81
96
  }
82
97
 
83
98
  async materializeTransferredActor(
@@ -122,27 +137,41 @@ export class ZLinkActorCreationCoordinator {
122
137
  this.options.actorMeshNameProvider
123
138
  ));
124
139
  const nativeActorNode = this.options.nativeActorNode ?? this.options.nativeActorNodeProvider?.();
140
+ let actor: ZLinkActor;
141
+ try {
142
+ actor = await factory.create(context, signal);
143
+ } catch (error) {
144
+ await this.discardStagingActor(state, nativeActorNode);
145
+ return { status: 'failed', error };
146
+ }
147
+ let meshName: string;
148
+ let nodeRid: RoutingId | undefined;
125
149
  try {
126
- const actor = await factory.create(context, signal);
127
150
  if (actor.context !== context || actor.context.actorId !== context.actorId) {
128
151
  throw new ZLinkConfigurationException(
129
152
  `Actor factory '${actorType}' must return an Actor bound to the exact supplied context.`
130
153
  );
131
154
  }
132
- const meshName = state.meshName ?? '';
155
+ meshName = state.meshName ?? '';
133
156
  if (meshName.length === 0 && state.nativeActorRef === undefined) {
134
157
  throw new ZLinkConfigurationException(
135
158
  `Actor '${actorId}' has no RouteMesh identity.`
136
159
  );
137
160
  }
138
- let nodeRid: RoutingId | undefined;
139
161
  if (nativeActorNode !== undefined) {
140
162
  const actorRef = state.ensureNativeActorRef(nativeActorNode, createRequest.nativeRequest);
141
163
  nodeRid = toFrameworkRoutingId(actorRef.nodeRid);
142
164
  } else {
143
165
  nodeRid = this.options.actorCreatedNodeRidProvider?.();
144
166
  }
145
- const response = nodeRid === undefined
167
+ } catch (error) {
168
+ await this.discardStagingActor(state, nativeActorNode);
169
+ throw error;
170
+ }
171
+
172
+ let response: ZLinkActorCreateResponse;
173
+ try {
174
+ response = nodeRid === undefined
146
175
  ? { accepted: true }
147
176
  : await this.options.actorCreatedNotifier?.(
148
177
  nodeRid,
@@ -150,11 +179,16 @@ export class ZLinkActorCreationCoordinator {
150
179
  createRequest.callbackRequest,
151
180
  signal
152
181
  ) ?? { accepted: true };
153
- if (!response.accepted) {
154
- await this.discardStagingActor(state, nativeActorNode);
155
- return { status: 'rejected', reply: response.reply };
156
- }
182
+ } catch (error) {
183
+ await this.discardStagingActor(state, nativeActorNode);
184
+ return { status: 'failed', error };
185
+ }
186
+ if (!response.accepted) {
187
+ await this.discardStagingActor(state, nativeActorNode);
188
+ return { status: 'rejected', reply: response.reply };
189
+ }
157
190
 
191
+ try {
158
192
  state.bindActor(actor, context);
159
193
  if (nativeActorNode !== undefined) {
160
194
  const actorRef = state.nativeActorRef!;
@@ -257,6 +257,10 @@ export type ZLinkActorCreationAttemptResult =
257
257
  | {
258
258
  readonly status: 'rejected';
259
259
  readonly reply?: unknown;
260
+ }
261
+ | {
262
+ readonly status: 'failed';
263
+ readonly error: unknown;
260
264
  };
261
265
 
262
266
  export class ZLinkActorRuntimeState {
@@ -379,6 +379,9 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
379
379
  ? { status: 'rejected' }
380
380
  : { status: 'rejected', reply: result.reply };
381
381
  }
382
+ if (result.status === 'failed') {
383
+ throw actorApplicationFailure(actorId, result.error);
384
+ }
382
385
  return result.reply === undefined
383
386
  ? { status: result.status, actor: result.actorRef }
384
387
  : { status: result.status, actor: result.actorRef, reply: result.reply };
@@ -720,12 +723,19 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
720
723
  const operation = state.getOrStartCreation(
721
724
  actorType,
722
725
  failIfExists,
723
- () => this.creation.createActor(actorId, actorType, state, createRequest, claimLocation, signal)
726
+ () => this.creation.createActor(
727
+ actorId,
728
+ actorType,
729
+ state,
730
+ createRequest,
731
+ claimLocation,
732
+ signal
733
+ )
724
734
  );
725
735
  if (operation.created) {
726
736
  void operation.task.then(
727
737
  (result) => {
728
- if (result.status === 'rejected') {
738
+ if (result.status !== 'created') {
729
739
  this.discardFailedState(actorId, state, operation.task);
730
740
  }
731
741
  },
@@ -745,7 +755,9 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
745
755
  };
746
756
  }
747
757
  if (operation.created) {
748
- return { status: 'rejected', reply: result.reply };
758
+ return result.status === 'failed'
759
+ ? { status: 'failed', error: result.error }
760
+ : { status: 'rejected', reply: result.reply };
749
761
  }
750
762
  } catch (error) {
751
763
  if (operation.created) {
@@ -936,16 +948,32 @@ export type ZLinkActorLocalCreateResult =
936
948
  | {
937
949
  readonly status: 'rejected';
938
950
  readonly reply?: unknown;
951
+ }
952
+ | {
953
+ readonly status: 'failed';
954
+ readonly error: unknown;
939
955
  };
940
956
 
941
957
  function requireCreatedActor(result: ZLinkActorLocalCreateResult, actorId: string): ZLinkActor {
942
- if (result.status !== 'rejected') return result.actor;
958
+ if (result.status === 'existing' || result.status === 'created') return result.actor;
959
+ if (result.status === 'failed') {
960
+ throw actorApplicationFailure(actorId, result.error);
961
+ }
943
962
  throw createInternalFrameworkException(
944
963
  ZLinkFrameworkInternalErrorKind.ActorCreateRejected,
945
964
  `Actor '${actorId}' create request was rejected.`
946
965
  );
947
966
  }
948
967
 
968
+ function actorApplicationFailure(actorId: string, error: unknown): ZLinkFrameworkException {
969
+ return createInternalFrameworkException(
970
+ ZLinkFrameworkInternalErrorKind.ActorCreateFailed,
971
+ `Actor '${actorId}' creation failed: ${error instanceof Error ? error.message : String(error)}`,
972
+ false,
973
+ error
974
+ );
975
+ }
976
+
949
977
  async function waitForCreationRetry(signal?: AbortSignal): Promise<void> {
950
978
  throwIfAborted(signal);
951
979
  await new Promise<void>((resolve, reject) => {
@@ -2,7 +2,10 @@ import { ZLinkFrameworkInternalErrorKind, createInternalFrameworkException } fr
2
2
  export class ZLinkRuntimeAdmissionGate {
3
3
  private readonly meshes = new Map<string, ZLinkMeshAdmissionState>();
4
4
 
5
+ constructor(private readonly ownerAdmissionOpen: () => boolean = () => true) {}
6
+
5
7
  get acceptsNewWork(): boolean {
8
+ if (!this.ownerAdmissionOpen()) return false;
6
9
  for (const state of this.meshes.values()) {
7
10
  if (state.sealed) return false;
8
11
  }
@@ -23,7 +26,7 @@ export class ZLinkRuntimeAdmissionGate {
23
26
  }
24
27
 
25
28
  accepts(meshName: string): boolean {
26
- return !this.requireState(meshName).sealed;
29
+ return this.ownerAdmissionOpen() && !this.requireState(meshName).sealed;
27
30
  }
28
31
 
29
32
  pending(meshName: string): number {
@@ -48,7 +51,7 @@ export class ZLinkRuntimeAdmissionGate {
48
51
 
49
52
  claim(meshName: string, operation: string): ZLinkApplicationWorkClaim {
50
53
  const state = this.requireState(meshName);
51
- if (!state.sealed) {
54
+ if (this.ownerAdmissionOpen() && !state.sealed) {
52
55
  state.active += 1;
53
56
  let closed = false;
54
57
  return {
@@ -65,7 +68,7 @@ export class ZLinkRuntimeAdmissionGate {
65
68
  }
66
69
  throw createInternalFrameworkException(
67
70
  state.sealedReason,
68
- `${operation} was rejected because the framework is draining.`
71
+ `${operation} was rejected because framework admission is closed.`
69
72
  );
70
73
  }
71
74
 
@@ -113,7 +116,7 @@ export class ZLinkRuntimeAdmissionGate {
113
116
  meshName === undefined
114
117
  ? this.firstSealedReason()
115
118
  : this.requireState(meshName).sealedReason,
116
- `${operation} was rejected because the framework is draining.`
119
+ `${operation} was rejected because framework admission is closed.`
117
120
  );
118
121
  }
119
122
 
@@ -130,7 +133,7 @@ export class ZLinkRuntimeAdmissionGate {
130
133
  reason === ZLinkFrameworkInternalErrorKind.RuntimeShutdown
131
134
  ? ZLinkFrameworkInternalErrorKind.RuntimeShutdown
132
135
  : ZLinkFrameworkInternalErrorKind.ActorCreateRejected,
133
- `Actor '${actorId}' create request was rejected because the framework is draining.`
136
+ `Actor '${actorId}' create request was rejected because framework admission is closed.`
134
137
  );
135
138
  }
136
139
 
@@ -157,7 +157,7 @@ export interface ZLinkBackendMeshNode {
157
157
  ): Promise<ServiceUserSpotOperationResult>;
158
158
  requestActorCreate(
159
159
  targetNodeRid: string,
160
- request: Omit<ServiceActorCreateRecord, 'kind' | 'correlation' | 'operation'>,
160
+ request: Omit<ServiceActorCreateRecord, 'kind' | 'correlation'>,
161
161
  timeoutMs: number
162
162
  ): Promise<ServiceUserSpotOperationResult>;
163
163
  connectPeer(options: {
@@ -503,6 +503,7 @@ export interface ZLinkBackendDealerSocket extends ZLinkBackendConnectableSocket
503
503
  peerWeight: number;
504
504
  sendHighWaterMark: number;
505
505
  receiveHighWaterMark: number;
506
+ receiveTimeoutMs: number;
506
507
  sendTimeoutMs: number;
507
508
  maxMessageSize: number;
508
509
  send(message: Message | readonly Message[]): Promise<void>;
@@ -536,6 +537,7 @@ export interface ZLinkBackendRouterSocket extends ZLinkBackendConnectableSocket
536
537
  peerWeight: number;
537
538
  sendHighWaterMark: number;
538
539
  receiveHighWaterMark: number;
540
+ receiveTimeoutMs: number;
539
541
  sendTimeoutMs: number;
540
542
  maxMessageSize: number;
541
543
  setRoutingId(routingId: RoutingId): void;
@@ -740,6 +742,7 @@ export interface ZLinkMeshBackendAdapter {
740
742
  options: {
741
743
  readonly meshName: string;
742
744
  readonly routingId?: RoutingId;
745
+ readonly receiveTimeoutMs?: number;
743
746
  readonly trustProfile?: string;
744
747
  readonly applicationJobQueue: import('../../application-jobs/contracts').ApplicationJobQueuePort;
745
748
  readonly applicationJobReceiveFlowFailureSink?: (error: unknown) => void;
@@ -13,6 +13,7 @@ export class ZLinkNodeMeshBackendAdapter implements ZLinkMeshBackendAdapter {
13
13
  options: {
14
14
  readonly meshName: string;
15
15
  readonly routingId?: string;
16
+ readonly receiveTimeoutMs?: number;
16
17
  readonly trustProfile?: string;
17
18
  readonly applicationJobQueue: import('../../application-jobs/contracts').ApplicationJobQueuePort;
18
19
  readonly applicationJobReceiveFlowFailureSink?: (error: unknown) => void;
@@ -29,7 +30,7 @@ export class ZLinkNodeMeshBackendAdapter implements ZLinkMeshBackendAdapter {
29
30
  return new ZLinkNodeRawMeshBackend(
30
31
  options.meshName,
31
32
  options.routingId,
32
- new ZLinkNodeRawBindingPort(bindingContext),
33
+ new ZLinkNodeRawBindingPort(bindingContext, options.receiveTimeoutMs),
33
34
  options.applicationJobQueue,
34
35
  options.applicationJobReceiveFlowFailureSink,
35
36
  options.peerAdmissionSealed
@@ -47,12 +47,16 @@ export type {
47
47
  } from '../raw-binding-port';
48
48
 
49
49
  export class ZLinkNodeRawBindingPort implements ZLinkRawBindingPort {
50
- constructor(private readonly sharedContext?: Context) {}
50
+ constructor(
51
+ private readonly sharedContext?: Context,
52
+ private readonly routerReceiveTimeoutMs?: number
53
+ ) {}
51
54
 
52
55
  createHost(): ZLinkRawHostPort {
53
56
  return new NodeRawHostPort(
54
57
  this.sharedContext ?? createContext(),
55
- this.sharedContext === undefined
58
+ this.sharedContext === undefined,
59
+ this.routerReceiveTimeoutMs
56
60
  );
57
61
  }
58
62
  }
@@ -63,12 +67,16 @@ class NodeRawHostPort implements ZLinkRawHostPort {
63
67
 
64
68
  constructor(
65
69
  private readonly context: Context,
66
- private readonly ownsContext: boolean
70
+ private readonly ownsContext: boolean,
71
+ private readonly routerReceiveTimeoutMs?: number
67
72
  ) {}
68
73
 
69
74
  createRouter(): ZLinkRawRouterPort {
70
75
  this.requireOpen();
71
- return this.own(new NodeRawRouterPort(createRouterSocket(this.context)));
76
+ return this.own(new NodeRawRouterPort(
77
+ createRouterSocket(this.context),
78
+ this.routerReceiveTimeoutMs
79
+ ));
72
80
  }
73
81
 
74
82
  createDealer(): ZLinkRawDealerPort {
@@ -230,11 +238,14 @@ class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLink
230
238
  }).disconnectRid(bindingRoutingId(routingId));
231
239
  }
232
240
 
233
- constructor(socket: RouterSocket) {
241
+ constructor(socket: RouterSocket, receiveTimeoutMs?: number) {
234
242
  super(socket);
235
243
  socket.options.handover = true;
236
244
  socket.options.mandatory = true;
237
245
  socket.options.probe = true;
246
+ if (receiveTimeoutMs !== undefined) {
247
+ socket.options.recvTimeout = receiveTimeoutMs;
248
+ }
238
249
  }
239
250
 
240
251
  localEndpoint(): string {
@@ -1008,7 +1008,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
1008
1008
 
1009
1009
  requestActorCreate(
1010
1010
  targetNodeRid: string,
1011
- request: Omit<ServiceActorCreateRecord, 'kind' | 'correlation' | 'operation'>,
1011
+ request: Omit<ServiceActorCreateRecord, 'kind' | 'correlation'>,
1012
1012
  timeoutMs: number
1013
1013
  ): Promise<ServiceUserSpotOperationResult> {
1014
1014
  return this.requireStateful().requestActorCreate(targetNodeRid, request, timeoutMs);
@@ -32,6 +32,7 @@ export function wrapSocket<T extends { close(): void }>(
32
32
  peerWeight?: number;
33
33
  sendHwm?: number;
34
34
  recvHwm?: number;
35
+ recvTimeout?: number;
35
36
  sendTimeout?: number;
36
37
  noDrop?: boolean;
37
38
  maxMsgSize?: bigint;
@@ -129,6 +130,12 @@ export function wrapSocket<T extends { close(): void }>(
129
130
  set receiveHighWaterMark(value: number) {
130
131
  requireSocketOptions(socket).recvHwm = value;
131
132
  },
133
+ get receiveTimeoutMs(): number {
134
+ return socket.options?.recvTimeout ?? 0;
135
+ },
136
+ set receiveTimeoutMs(value: number) {
137
+ requireSocketOptions(socket).recvTimeout = value;
138
+ },
132
139
  get sendTimeoutMs(): number {
133
140
  return socket.options?.sendTimeout ?? 0;
134
141
  },
@@ -53,6 +53,15 @@ class ZLinkLiveSocketConfig implements ZLinkSocketConfig {
53
53
  this.socket.receiveHighWaterMark = value;
54
54
  }
55
55
 
56
+ get receiveTimeoutMs(): number {
57
+ return this.socket.receiveTimeoutMs;
58
+ }
59
+
60
+ set receiveTimeoutMs(value: number) {
61
+ requireValidSendTimeoutMs('receiveTimeoutMs', value);
62
+ this.socket.receiveTimeoutMs = value;
63
+ }
64
+
56
65
  get sendTimeoutMs(): number {
57
66
  return this.socket.sendTimeoutMs;
58
67
  }
@@ -1854,6 +1854,7 @@ function applySocketConfig(
1854
1854
  config: {
1855
1855
  readonly sendHighWaterMark?: number;
1856
1856
  readonly receiveHighWaterMark?: number;
1857
+ readonly receiveTimeoutMs?: number;
1857
1858
  readonly sendTimeoutMs?: number;
1858
1859
  readonly maxMessageSize?: number;
1859
1860
  }
@@ -1864,6 +1865,9 @@ function applySocketConfig(
1864
1865
  if (config.receiveHighWaterMark !== undefined) {
1865
1866
  socket.receiveHighWaterMark = config.receiveHighWaterMark;
1866
1867
  }
1868
+ if (config.receiveTimeoutMs !== undefined) {
1869
+ socket.receiveTimeoutMs = config.receiveTimeoutMs;
1870
+ }
1867
1871
  if (config.sendTimeoutMs !== undefined) {
1868
1872
  socket.sendTimeoutMs = config.sendTimeoutMs;
1869
1873
  }
@@ -750,14 +750,10 @@ export class ServiceStatefulRuntime {
750
750
 
751
751
  requestActorCreate(
752
752
  targetNodeRid: string,
753
- request: Omit<ServiceActorCreateRecord, 'kind' | 'correlation' | 'operation'>,
753
+ request: Omit<ServiceActorCreateRecord, 'kind' | 'correlation'>,
754
754
  timeoutMs: number
755
755
  ): Promise<ServiceUserSpotOperationResult> {
756
756
  const correlation = this.nextUserSpotOperation++;
757
- const operation = {
758
- high: this.nodeGeneration,
759
- low: correlation
760
- };
761
757
  return this.requestUserSpotOperation(
762
758
  targetNodeRid,
763
759
  encodeActorCreateHeader({
@@ -765,7 +761,7 @@ export class ServiceStatefulRuntime {
765
761
  sourceNodeRid: this.nodeRid,
766
762
  sourceNodeGeneration: this.nodeGeneration,
767
763
  correlation,
768
- operation
764
+ operation: request.operation
769
765
  }),
770
766
  correlation,
771
767
  'actorCreate',