@zlink-systems/framework 0.29.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/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/foundation/service-stateful-runtime.js +27 -19
- 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/locations/domain-store-contract.d.ts +2 -1
- package/dist/runtime/locations/in-memory-authority-store.d.ts +1 -0
- package/dist/runtime/locations/in-memory-authority-store.js +32 -11
- package/dist/runtime/locations/in-memory-location-store.d.ts +1 -1
- package/dist/runtime/locations/in-memory-location-store.js +12 -10
- package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
- package/dist/runtime/locations/location-store-repository.d.ts +1 -0
- package/dist/runtime/locations/location-store-repository.js +54 -21
- 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 +10 -1
- package/package.json +2 -2
- package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
- package/src/runtime/actors/index.ts +77 -42
- package/src/runtime/foundation/service-stateful-runtime.ts +49 -43
- 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/locations/domain-store-contract.ts +1 -1
- package/src/runtime/locations/in-memory-authority-store.ts +47 -10
- package/src/runtime/locations/in-memory-location-store.ts +14 -10
- package/src/runtime/locations/internal-location-contracts.ts +6 -0
- package/src/runtime/locations/location-store-repository.ts +82 -28
- 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 +10 -4
|
@@ -99,11 +99,14 @@ export class ZLinkInMemoryLocationStore
|
|
|
99
99
|
private readonly authority: ZLinkInMemoryAuthorityStore;
|
|
100
100
|
|
|
101
101
|
constructor(private readonly now: () => Date = () => new Date()) {
|
|
102
|
+
const isOwnerLive = (owner: ZLinkLocationOwnerToken): boolean =>
|
|
103
|
+
this.liveOwnerLease(owner.ownerId, this.now())?.token.leaseGeneration ===
|
|
104
|
+
owner.leaseGeneration;
|
|
102
105
|
this.authority = new ZLinkInMemoryAuthorityStore(
|
|
103
106
|
{
|
|
107
|
+
isOwnerLive,
|
|
104
108
|
isTargetLive: (key, lifecycleGeneration, owner) => {
|
|
105
109
|
const descriptor = this.meshNodes.rows.get(meshNodeKey(key.meshName, key.rid));
|
|
106
|
-
const lease = this.leases.get(owner.ownerId);
|
|
107
110
|
return (
|
|
108
111
|
descriptor !== undefined &&
|
|
109
112
|
descriptor.lifecycleGeneration === lifecycleGeneration &&
|
|
@@ -111,9 +114,7 @@ export class ZLinkInMemoryLocationStore
|
|
|
111
114
|
descriptor.leaseGeneration === owner.leaseGeneration &&
|
|
112
115
|
descriptor.objectRole === ZLinkObjectRole.Server &&
|
|
113
116
|
descriptor.state === ZLinkFrameworkRuntimeState.Serving &&
|
|
114
|
-
|
|
115
|
-
lease.token.leaseGeneration === owner.leaseGeneration &&
|
|
116
|
-
lease.leaseExpiresAt.getTime() > this.now().getTime()
|
|
117
|
+
isOwnerLive(owner)
|
|
117
118
|
);
|
|
118
119
|
},
|
|
119
120
|
placementCapacityAvailable: (key, requested, reserved, active) => {
|
|
@@ -566,7 +567,8 @@ export class ZLinkInMemoryLocationStore
|
|
|
566
567
|
const storeNow = this.now();
|
|
567
568
|
return pageRows(
|
|
568
569
|
this.fanoutPublishers,
|
|
569
|
-
(row) =>
|
|
570
|
+
(row) =>
|
|
571
|
+
row.channelName === channelName && this.liveOwnerLease(row.ownerId, storeNow) !== undefined,
|
|
570
572
|
page
|
|
571
573
|
);
|
|
572
574
|
}
|
|
@@ -628,7 +630,7 @@ export class ZLinkInMemoryLocationStore
|
|
|
628
630
|
if (
|
|
629
631
|
intent === ZLinkLocationWriteIntent.NewClaim &&
|
|
630
632
|
current !== undefined &&
|
|
631
|
-
this.
|
|
633
|
+
this.liveOwnerLease(current.ownerId, updatedAt) !== undefined
|
|
632
634
|
) {
|
|
633
635
|
return rejectedConflict();
|
|
634
636
|
}
|
|
@@ -706,7 +708,7 @@ export class ZLinkInMemoryLocationStore
|
|
|
706
708
|
if (
|
|
707
709
|
intent === ZLinkLocationWriteIntent.NewClaim &&
|
|
708
710
|
current !== undefined &&
|
|
709
|
-
this.
|
|
711
|
+
this.liveOwnerLease(current.ownerId, updatedAt) !== undefined
|
|
710
712
|
) {
|
|
711
713
|
return rejectedConflict();
|
|
712
714
|
}
|
|
@@ -1155,7 +1157,7 @@ export class ZLinkInMemoryLocationStore
|
|
|
1155
1157
|
if (
|
|
1156
1158
|
intent === ZLinkLocationWriteIntent.NewClaim &&
|
|
1157
1159
|
current !== undefined &&
|
|
1158
|
-
this.
|
|
1160
|
+
this.liveOwnerLease(ownerOf(current), updatedAt) !== undefined
|
|
1159
1161
|
) {
|
|
1160
1162
|
return rejectedConflict();
|
|
1161
1163
|
}
|
|
@@ -1252,9 +1254,11 @@ export class ZLinkInMemoryLocationStore
|
|
|
1252
1254
|
return removed;
|
|
1253
1255
|
}
|
|
1254
1256
|
|
|
1255
|
-
private
|
|
1257
|
+
private liveOwnerLease(ownerId: string, now: Date): InMemoryOwnerLease | undefined {
|
|
1256
1258
|
const lease = this.leases.get(ownerId);
|
|
1257
|
-
return lease !== undefined && lease.leaseExpiresAt.getTime() > now.getTime()
|
|
1259
|
+
return lease !== undefined && lease.leaseExpiresAt.getTime() > now.getTime()
|
|
1260
|
+
? lease
|
|
1261
|
+
: undefined;
|
|
1258
1262
|
}
|
|
1259
1263
|
|
|
1260
1264
|
private bump(kind: ZLinkLocationKind, meshName: string | undefined): void {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export * from '../../contracts/Locations';
|
|
2
|
+
import type { ZLinkObjectReserveRequest as ReserveRequest } from '../../contracts/Locations/Authority';
|
|
3
|
+
import type { ZLinkRelocationConfiguration } from '../../contracts/Configuration/RegistrationTypes';
|
|
4
|
+
|
|
5
|
+
export interface ZLinkObjectReserveRequest extends ReserveRequest {
|
|
6
|
+
readonly actorRelocationPolicy?: ZLinkRelocationConfiguration<unknown>['kind'];
|
|
7
|
+
}
|
|
2
8
|
export type * from '../../contracts/Locations/Authority';
|
|
3
9
|
export type {
|
|
4
10
|
ZLinkActorLocation,
|
|
@@ -1130,19 +1130,31 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1130
1130
|
expectedStoreVersion: string,
|
|
1131
1131
|
signal?: AbortSignal
|
|
1132
1132
|
): Promise<boolean> {
|
|
1133
|
+
return (
|
|
1134
|
+
(await this.tryReleaseEndedReservation(key, expectedStoreVersion, signal)) === 'reclaimed'
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
private async tryReleaseEndedReservation(
|
|
1139
|
+
key: ZLinkAuthorityKey,
|
|
1140
|
+
expectedStoreVersion: string,
|
|
1141
|
+
signal?: AbortSignal,
|
|
1142
|
+
request?: ZLinkObjectReserveRequest
|
|
1143
|
+
): Promise<'notReclaimable' | 'conflict' | 'reclaimed'> {
|
|
1133
1144
|
const rowKey = authorityKey(key.value);
|
|
1134
1145
|
const current = await this.provider.read(rowKey, signal);
|
|
1135
1146
|
if (current.kind === 'missing' || current.value.version.value !== expectedStoreVersion)
|
|
1136
|
-
return
|
|
1147
|
+
return 'conflict';
|
|
1137
1148
|
const record = decodeAuthorityRecord(current.value.bytes);
|
|
1138
1149
|
const snapshot = record.snapshot;
|
|
1139
1150
|
if (
|
|
1140
|
-
snapshot.allocation.state
|
|
1141
|
-
|
|
1151
|
+
(snapshot.allocation.state === 'active'
|
|
1152
|
+
? request?.key.kind !== 'actor'
|
|
1153
|
+
: snapshot.pendingCreation === undefined) ||
|
|
1142
1154
|
record.aggregate !== undefined ||
|
|
1143
1155
|
hasRelocationAuthority(snapshot.payload)
|
|
1144
1156
|
)
|
|
1145
|
-
return
|
|
1157
|
+
return 'notReclaimable';
|
|
1146
1158
|
const leaseKey = ownerKey(snapshot.ownerId);
|
|
1147
1159
|
const descriptorKey = meshKey(
|
|
1148
1160
|
snapshot.allocation.descriptor.meshName,
|
|
@@ -1159,27 +1171,56 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1159
1171
|
]);
|
|
1160
1172
|
if (lease.kind === 'found' && lease.value.expiresAt === undefined)
|
|
1161
1173
|
throw new Error('Location Store owner lease record is invalid.');
|
|
1162
|
-
|
|
1163
|
-
|
|
1174
|
+
const ownerLive = sameLiveOwner(lease, snapshot);
|
|
1175
|
+
if (snapshot.allocation.state === 'active') {
|
|
1176
|
+
if (ownerLive) return 'notReclaimable';
|
|
1177
|
+
if (request?.actorRelocationPolicy !== 'disabled')
|
|
1178
|
+
throw new ZLinkFrameworkException(
|
|
1179
|
+
ZLinkFrameworkErrorKind.Unavailable,
|
|
1180
|
+
'Actor owner lease is unavailable.'
|
|
1181
|
+
);
|
|
1182
|
+
} else if (
|
|
1183
|
+
ownerLive &&
|
|
1164
1184
|
descriptor.kind === 'found' &&
|
|
1165
1185
|
reviveMeshDescriptor(
|
|
1166
1186
|
decodeCanonicalDescriptorRecord<ZLinkMeshNodeDescriptor>(descriptor.value.bytes).descriptor
|
|
1167
1187
|
).lifecycleGeneration === snapshot.allocation.descriptorLifecycleGeneration
|
|
1168
1188
|
)
|
|
1169
|
-
return
|
|
1189
|
+
return 'notReclaimable';
|
|
1170
1190
|
const mutations: ZLinkStoreWriteRequest['mutations'][number][] = [
|
|
1171
1191
|
{ kind: 'delete', key: rowKey }
|
|
1172
1192
|
];
|
|
1173
1193
|
if (capacity.kind === 'found') {
|
|
1174
1194
|
const stored = decodeJson<CapacityRecord>(capacity.value.bytes);
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1195
|
+
try {
|
|
1196
|
+
mutations.push({
|
|
1197
|
+
kind: 'put',
|
|
1198
|
+
key: capacityRowKey,
|
|
1199
|
+
bytes: encodeJson({
|
|
1200
|
+
active:
|
|
1201
|
+
snapshot.allocation.state === 'active'
|
|
1202
|
+
? subtractCapacity(stored.active, snapshot.allocation.capacity)
|
|
1203
|
+
: stored.active,
|
|
1204
|
+
pending:
|
|
1205
|
+
snapshot.allocation.state === 'reserved'
|
|
1206
|
+
? subtractCapacity(stored.pending, snapshot.allocation.capacity)
|
|
1207
|
+
: stored.pending
|
|
1208
|
+
} satisfies CapacityRecord)
|
|
1209
|
+
});
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
if (
|
|
1212
|
+
!(error instanceof ZLinkFrameworkException) ||
|
|
1213
|
+
error.kind !== ZLinkFrameworkErrorKind.DataLost
|
|
1214
|
+
)
|
|
1215
|
+
throw error;
|
|
1216
|
+
const observed = await this.provider.read(rowKey, signal);
|
|
1217
|
+
if (
|
|
1218
|
+
observed.kind === 'missing' ||
|
|
1219
|
+
observed.value.version.value !== current.value.version.value
|
|
1220
|
+
)
|
|
1221
|
+
return 'conflict';
|
|
1222
|
+
throw error;
|
|
1223
|
+
}
|
|
1183
1224
|
}
|
|
1184
1225
|
const result = await this.provider.write(
|
|
1185
1226
|
{
|
|
@@ -1193,7 +1234,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1193
1234
|
},
|
|
1194
1235
|
signal
|
|
1195
1236
|
);
|
|
1196
|
-
return result.kind === 'applied';
|
|
1237
|
+
return result.kind === 'applied' ? 'reclaimed' : 'conflict';
|
|
1197
1238
|
}
|
|
1198
1239
|
|
|
1199
1240
|
override async reserve(
|
|
@@ -1203,6 +1244,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1203
1244
|
): Promise<ZLinkObjectReserveResult> {
|
|
1204
1245
|
const encodedAuthorityKey = encodeAuthorityKey(request.key.kind, request.key.globalId);
|
|
1205
1246
|
const reservationId = randomUUID();
|
|
1247
|
+
let reclaimVersion: string | undefined;
|
|
1206
1248
|
for (;;) {
|
|
1207
1249
|
throwIfOperationExpired(signal, deadlineUnixMs);
|
|
1208
1250
|
const rowKey = authorityKey(encodedAuthorityKey.value);
|
|
@@ -1223,19 +1265,23 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1223
1265
|
) {
|
|
1224
1266
|
return { kind: 'typeMismatch', current: snapshot };
|
|
1225
1267
|
}
|
|
1226
|
-
if (snapshot.
|
|
1227
|
-
return { kind: 'alreadyExists', current: snapshot };
|
|
1228
|
-
}
|
|
1229
|
-
if (
|
|
1230
|
-
!(await this.releaseEndedReservation(
|
|
1231
|
-
encodedAuthorityKey,
|
|
1232
|
-
current.value.version.value,
|
|
1233
|
-
signal
|
|
1234
|
-
))
|
|
1235
|
-
)
|
|
1268
|
+
if (reclaimVersion !== undefined && snapshot.storeVersion.value !== reclaimVersion)
|
|
1236
1269
|
return { kind: 'conflict', current: snapshot };
|
|
1270
|
+
const reclaim = await this.tryReleaseEndedReservation(
|
|
1271
|
+
encodedAuthorityKey,
|
|
1272
|
+
current.value.version.value,
|
|
1273
|
+
signal,
|
|
1274
|
+
request
|
|
1275
|
+
);
|
|
1276
|
+
if (reclaim === 'notReclaimable')
|
|
1277
|
+
return snapshot.allocation.state === 'active'
|
|
1278
|
+
? { kind: 'alreadyExists', current: snapshot }
|
|
1279
|
+
: { kind: 'conflict', current: snapshot };
|
|
1280
|
+
reclaimVersion = reclaim === 'conflict' ? snapshot.storeVersion.value : undefined;
|
|
1237
1281
|
continue;
|
|
1238
1282
|
}
|
|
1283
|
+
if (reclaimVersion !== undefined)
|
|
1284
|
+
return { kind: 'conflict', current: { kind: 'missing', storeNow: current.storeNow } };
|
|
1239
1285
|
const [descriptorRead, leaseRead] = await Promise.all([
|
|
1240
1286
|
this.provider.read(descriptorKey, signal),
|
|
1241
1287
|
this.provider.read(leaseKey, signal)
|
|
@@ -4040,13 +4086,21 @@ function adjustCapacity(
|
|
|
4040
4086
|
if (delta.spotType !== undefined) {
|
|
4041
4087
|
const key = capacityTypeKey(delta.spotType);
|
|
4042
4088
|
const next = (spotTypes[key] ?? 0) + direction * delta.spotType.count;
|
|
4043
|
-
if (next < 0)
|
|
4089
|
+
if (next < 0)
|
|
4090
|
+
throw new ZLinkFrameworkException(
|
|
4091
|
+
ZLinkFrameworkErrorKind.DataLost,
|
|
4092
|
+
'Provider capacity counter underflow.'
|
|
4093
|
+
);
|
|
4044
4094
|
if (next === 0) delete spotTypes[key];
|
|
4045
4095
|
else spotTypes[key] = next;
|
|
4046
4096
|
}
|
|
4047
4097
|
const actors = usage.actors + direction * delta.actors;
|
|
4048
4098
|
const spots = usage.spots + direction * delta.spots;
|
|
4049
|
-
if (actors < 0 || spots < 0)
|
|
4099
|
+
if (actors < 0 || spots < 0)
|
|
4100
|
+
throw new ZLinkFrameworkException(
|
|
4101
|
+
ZLinkFrameworkErrorKind.DataLost,
|
|
4102
|
+
'Provider capacity counter underflow.'
|
|
4103
|
+
);
|
|
4050
4104
|
return { actors, spots, spotTypes };
|
|
4051
4105
|
}
|
|
4052
4106
|
|
|
@@ -378,6 +378,10 @@ export class ZLinkEntrySpotActivation {
|
|
|
378
378
|
return this.spotSerialExecutor.executeActor(actorId, operation);
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
terminateActor(actorId: string, terminal: () => Promise<void>): Promise<void> {
|
|
382
|
+
return this.spotSerialExecutor.terminateActor(actorId, terminal);
|
|
383
|
+
}
|
|
384
|
+
|
|
381
385
|
/**
|
|
382
386
|
* Entry Spot membership does not have an application admission callback.
|
|
383
387
|
* The shared core round-trip therefore accepts a valid returning Actor and
|
|
@@ -783,6 +783,13 @@ export class ZLinkSpotNodeRuntimeManager {
|
|
|
783
783
|
return activation?.notifyJoinActor(actor, signal) ?? Promise.resolve();
|
|
784
784
|
}
|
|
785
785
|
|
|
786
|
+
terminateActorActivation(actor: ZLinkActor, terminal: () => Promise<void>): Promise<void> {
|
|
787
|
+
const activation = this.entryActivations.get(actor.context.meshName);
|
|
788
|
+
return activation === undefined
|
|
789
|
+
? terminal()
|
|
790
|
+
: activation.terminateActor(actor.context.actorId, terminal);
|
|
791
|
+
}
|
|
792
|
+
|
|
786
793
|
notifyPrimaryEntrySpotActorLeft(
|
|
787
794
|
actor: ZLinkActor,
|
|
788
795
|
signal?: AbortSignal,
|
|
@@ -51,10 +51,8 @@ export class ZLinkSpotSerialExecutor {
|
|
|
51
51
|
operation: (serial: ZLinkSpotSerialTurnExecutor) => Promise<T> | T,
|
|
52
52
|
workOptions?: ZLinkSerialWorkOptions
|
|
53
53
|
): Promise<T> {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
workOptions
|
|
57
|
-
);
|
|
54
|
+
const serial = this.actorSerial(actorId);
|
|
55
|
+
return this.actorExecutor(actorId).execute(() => operation(serial), workOptions);
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
/** @internal Restores a durable Actor FIFO prefix without starting it. */
|
|
@@ -102,6 +100,14 @@ export class ZLinkSpotSerialExecutor {
|
|
|
102
100
|
return this.spotSerial.execute(operation, { ...workOptions, lane: 'lifecycle' });
|
|
103
101
|
}
|
|
104
102
|
|
|
103
|
+
async terminateActor(actorId: string, terminal: () => Promise<void>): Promise<void> {
|
|
104
|
+
const executor = this.actorExecutor(actorId);
|
|
105
|
+
await executor.close();
|
|
106
|
+
await terminal();
|
|
107
|
+
if (this.actorExecutors.get(actorId) === executor) this.actorExecutors.delete(actorId);
|
|
108
|
+
this.actorSerials.delete(actorId);
|
|
109
|
+
}
|
|
110
|
+
|
|
105
111
|
/**
|
|
106
112
|
* Makes all child queues unreachable and completes them in one synchronous
|
|
107
113
|
* ownership turn. Already accepted work remains drainable.
|