effect 4.0.2 → 4.0.3
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/Context.js +3 -3
- package/dist/Context.js.map +1 -1
- package/dist/Crypto.d.ts +33 -5
- package/dist/Crypto.d.ts.map +1 -1
- package/dist/Crypto.js +8 -2
- package/dist/Crypto.js.map +1 -1
- package/dist/DateTime.d.ts +9 -6
- package/dist/DateTime.d.ts.map +1 -1
- package/dist/DateTime.js +9 -6
- package/dist/DateTime.js.map +1 -1
- package/dist/Duration.d.ts +6 -6
- package/dist/Duration.d.ts.map +1 -1
- package/dist/Duration.js +27 -8
- package/dist/Duration.js.map +1 -1
- package/dist/FileSystem.d.ts +44 -2
- package/dist/FileSystem.d.ts.map +1 -1
- package/dist/FileSystem.js.map +1 -1
- package/dist/Schema.d.ts +473 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +355 -4
- package/dist/Schema.js.map +1 -1
- package/dist/Stdio.d.ts +7 -1
- package/dist/Stdio.d.ts.map +1 -1
- package/dist/Stdio.js +1 -0
- package/dist/Stdio.js.map +1 -1
- package/dist/Stream.d.ts +11 -11
- package/dist/Stream.js +5 -5
- package/dist/ai/McpServer.d.ts +6 -0
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +65 -23
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js +5 -0
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
- package/dist/ai/internal/mcpProtocol.js.map +1 -1
- package/dist/ai/internal/mcpRuntime.js +1 -0
- package/dist/ai/internal/mcpRuntime.js.map +1 -1
- package/dist/cli/CliOutput.d.ts +27 -0
- package/dist/cli/CliOutput.d.ts.map +1 -1
- package/dist/cli/CliOutput.js +33 -10
- package/dist/cli/CliOutput.js.map +1 -1
- package/dist/cli/Command.d.ts.map +1 -1
- package/dist/cli/Command.js +6 -3
- package/dist/cli/Command.js.map +1 -1
- package/dist/cli/Completions.d.ts +3 -3
- package/dist/cli/Completions.d.ts.map +1 -1
- package/dist/cli/Completions.js +8 -5
- package/dist/cli/Completions.js.map +1 -1
- package/dist/cli/GlobalFlag.d.ts +5 -4
- package/dist/cli/GlobalFlag.d.ts.map +1 -1
- package/dist/cli/GlobalFlag.js +3 -3
- package/dist/cli/GlobalFlag.js.map +1 -1
- package/dist/cli/Prompt.d.ts +17 -1
- package/dist/cli/Prompt.d.ts.map +1 -1
- package/dist/cli/Prompt.js +119 -114
- package/dist/cli/Prompt.js.map +1 -1
- package/dist/cli/internal/ansi.d.ts +0 -1
- package/dist/cli/internal/ansi.d.ts.map +1 -1
- package/dist/cli/internal/ansi.js +20 -3
- package/dist/cli/internal/ansi.js.map +1 -1
- package/dist/cli/internal/completions/powershell.d.ts +2 -0
- package/dist/cli/internal/completions/powershell.d.ts.map +1 -0
- package/dist/cli/internal/completions/powershell.js +273 -0
- package/dist/cli/internal/completions/powershell.js.map +1 -0
- package/dist/cli/internal/completions/zsh.js +3 -2
- package/dist/cli/internal/completions/zsh.js.map +1 -1
- package/dist/cli/internal/wizard.d.ts +3 -3
- package/dist/cli/internal/wizard.d.ts.map +1 -1
- package/dist/cli/internal/wizard.js +21 -18
- package/dist/cli/internal/wizard.js.map +1 -1
- package/dist/cluster/ClusterSchema.d.ts +28 -0
- package/dist/cluster/ClusterSchema.d.ts.map +1 -1
- package/dist/cluster/ClusterSchema.js +30 -0
- package/dist/cluster/ClusterSchema.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +10 -18
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts +18 -3
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +31 -11
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Runners.d.ts.map +1 -1
- package/dist/cluster/Runners.js +8 -2
- package/dist/cluster/Runners.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +181 -62
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts +59 -19
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +84 -55
- package/dist/cluster/SqlMessageStorage.js.map +1 -1
- package/dist/cluster/SqlRunnerStorage.d.ts +68 -19
- package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/cluster/SqlRunnerStorage.js +191 -113
- package/dist/cluster/SqlRunnerStorage.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +104 -37
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/http/FindMyWay/internal/router.js +3 -6
- package/dist/http/FindMyWay/internal/router.js.map +1 -1
- package/dist/http/HttpClient.d.ts +6 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +39 -2
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpTraceContext.d.ts.map +1 -1
- package/dist/http/HttpTraceContext.js +4 -2
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/internal/effect.js +12 -7
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/net/AddressResolver.d.ts +145 -0
- package/dist/net/AddressResolver.d.ts.map +1 -0
- package/dist/net/AddressResolver.js +114 -0
- package/dist/net/AddressResolver.js.map +1 -0
- package/dist/net/Dns.d.ts +596 -0
- package/dist/net/Dns.d.ts.map +1 -0
- package/dist/net/Dns.js +549 -0
- package/dist/net/Dns.js.map +1 -0
- package/dist/net/Host.d.ts +320 -0
- package/dist/net/Host.d.ts.map +1 -0
- package/dist/net/Host.js +342 -0
- package/dist/net/Host.js.map +1 -0
- package/dist/net/IpInterface.d.ts +76 -3
- package/dist/net/IpInterface.d.ts.map +1 -1
- package/dist/net/IpInterface.js +53 -4
- package/dist/net/IpInterface.js.map +1 -1
- package/dist/net/IpNetwork.d.ts +82 -8
- package/dist/net/IpNetwork.d.ts.map +1 -1
- package/dist/net/IpNetwork.js +60 -4
- package/dist/net/IpNetwork.js.map +1 -1
- package/dist/net/NetAddress.d.ts +388 -35
- package/dist/net/NetAddress.d.ts.map +1 -1
- package/dist/net/NetAddress.js +350 -34
- package/dist/net/NetAddress.js.map +1 -1
- package/dist/net/index.d.ts +15 -0
- package/dist/net/index.d.ts.map +1 -1
- package/dist/net/index.js +15 -0
- package/dist/net/index.js.map +1 -1
- package/dist/observability/OtlpMetrics.d.ts.map +1 -1
- package/dist/observability/OtlpMetrics.js +15 -21
- package/dist/observability/OtlpMetrics.js.map +1 -1
- package/dist/observability/PrometheusMetrics.js +1 -0
- package/dist/observability/PrometheusMetrics.js.map +1 -1
- package/dist/persistence/PersistedQueue.d.ts +32 -3
- package/dist/persistence/PersistedQueue.d.ts.map +1 -1
- package/dist/persistence/PersistedQueue.js +75 -16
- package/dist/persistence/PersistedQueue.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts +22 -6
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +12 -5
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts +6 -0
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +42 -20
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/socket/DatagramSocket.d.ts +456 -0
- package/dist/socket/DatagramSocket.d.ts.map +1 -0
- package/dist/socket/DatagramSocket.js +535 -0
- package/dist/socket/DatagramSocket.js.map +1 -0
- package/dist/socket/Socket.d.ts +14 -1
- package/dist/socket/Socket.d.ts.map +1 -1
- package/dist/socket/Socket.js +9 -1
- package/dist/socket/Socket.js.map +1 -1
- package/dist/socket/index.d.ts +5 -0
- package/dist/socket/index.d.ts.map +1 -1
- package/dist/socket/index.js +5 -0
- package/dist/socket/index.js.map +1 -1
- package/dist/sql/Migrator.d.ts.map +1 -1
- package/dist/sql/Migrator.js +7 -2
- package/dist/sql/Migrator.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +7 -1
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +31 -6
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/testing/TestCrypto.d.ts +73 -0
- package/dist/testing/TestCrypto.d.ts.map +1 -0
- package/dist/testing/TestCrypto.js +87 -0
- package/dist/testing/TestCrypto.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/workflow/DurableDeferred.d.ts +54 -0
- package/dist/workflow/DurableDeferred.d.ts.map +1 -1
- package/dist/workflow/DurableDeferred.js +78 -49
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +2 -1
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +1 -1
- package/src/Context.ts +3 -3
- package/src/Crypto.ts +43 -9
- package/src/DateTime.ts +9 -6
- package/src/Duration.ts +36 -16
- package/src/FileSystem.ts +48 -2
- package/src/Schema.ts +669 -5
- package/src/Stdio.ts +9 -2
- package/src/Stream.ts +11 -11
- package/src/ai/McpServer.ts +63 -23
- package/src/ai/internal/mcpProtocol/v2026_07_28.ts +5 -0
- package/src/ai/internal/mcpProtocol.ts +3 -0
- package/src/ai/internal/mcpRuntime.ts +2 -0
- package/src/cli/CliOutput.ts +33 -16
- package/src/cli/Command.ts +4 -3
- package/src/cli/Completions.ts +9 -6
- package/src/cli/GlobalFlag.ts +19 -11
- package/src/cli/Prompt.ts +159 -149
- package/src/cli/internal/ansi.ts +26 -3
- package/src/cli/internal/completions/powershell.ts +322 -0
- package/src/cli/internal/completions/zsh.ts +4 -2
- package/src/cli/internal/wizard.ts +34 -26
- package/src/cluster/ClusterSchema.ts +32 -0
- package/src/cluster/ClusterWorkflowEngine.ts +13 -24
- package/src/cluster/MessageStorage.ts +45 -14
- package/src/cluster/Runners.ts +8 -2
- package/src/cluster/Sharding.ts +207 -49
- package/src/cluster/SqlMessageStorage.ts +118 -84
- package/src/cluster/SqlRunnerStorage.ts +224 -128
- package/src/cluster/internal/entityManager.ts +151 -55
- package/src/http/FindMyWay/internal/router.ts +4 -7
- package/src/http/HttpClient.ts +56 -2
- package/src/http/HttpTraceContext.ts +8 -2
- package/src/internal/effect.ts +15 -13
- package/src/internal/version.ts +1 -1
- package/src/net/AddressResolver.ts +222 -0
- package/src/net/Dns.ts +1035 -0
- package/src/net/Host.ts +472 -0
- package/src/net/IpInterface.ts +117 -7
- package/src/net/IpNetwork.ts +116 -17
- package/src/net/NetAddress.ts +611 -76
- package/src/net/index.ts +18 -0
- package/src/observability/OtlpMetrics.ts +12 -23
- package/src/observability/PrometheusMetrics.ts +1 -0
- package/src/persistence/PersistedQueue.ts +146 -53
- package/src/rpc/RpcClient.ts +41 -19
- package/src/rpc/RpcServer.ts +60 -25
- package/src/socket/DatagramSocket.ts +919 -0
- package/src/socket/Socket.ts +22 -2
- package/src/socket/index.ts +6 -0
- package/src/sql/Migrator.ts +21 -5
- package/src/sql/SqlClient.ts +39 -15
- package/src/testing/TestCrypto.ts +94 -0
- package/src/testing/index.ts +6 -0
- package/src/workflow/DurableDeferred.ts +123 -48
- package/src/workflow/WorkflowEngine.ts +2 -1
package/dist/cluster/Sharding.js
CHANGED
|
@@ -118,6 +118,13 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
118
118
|
// the active shards are the ones that we have acquired the lock for
|
|
119
119
|
const acquiredShards = MutableHashSet.empty();
|
|
120
120
|
const activeShardsLatch = yield* Latch.make(false);
|
|
121
|
+
// open once shutdown starts handing every shard off
|
|
122
|
+
const handoffLatch = Latch.makeUnsafe(false);
|
|
123
|
+
// open once the handoff has released every shard lock
|
|
124
|
+
const handedOffLatch = Latch.makeUnsafe(false);
|
|
125
|
+
// serializes runner registration with unregistration, so a registration
|
|
126
|
+
// in flight cannot re-advertise a runner that is shutting down
|
|
127
|
+
const withRegistrationLock = Semaphore.makeUnsafe(1).withPermits(1);
|
|
121
128
|
const events = yield* PubSub.unbounded();
|
|
122
129
|
const getRegistrationEvents = Stream.fromPubSub(events);
|
|
123
130
|
const isLocalRunner = address => {
|
|
@@ -131,6 +138,23 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
131
138
|
function isEntityOnLocalShards(address) {
|
|
132
139
|
return MutableHashSet.has(acquiredShards, address.shardId);
|
|
133
140
|
}
|
|
141
|
+
function isAwaitingShardLock(shardId) {
|
|
142
|
+
return !MutableRef.get(isShutdown) && MutableHashSet.has(selfShards, shardId) && !MutableHashSet.has(acquiredShards, shardId);
|
|
143
|
+
}
|
|
144
|
+
// Requests for a shard assigned to this runner wait for its lock, for at most
|
|
145
|
+
// `shardLockExpiration`, instead of bouncing while the previous owner drains.
|
|
146
|
+
// Each notification opens the current latch and replaces it, so a waiter that
|
|
147
|
+
// checked its shard before a notification cannot miss it.
|
|
148
|
+
let shardLocksChanged = Latch.makeUnsafe(false);
|
|
149
|
+
const notifyShardLockWaiters = Effect.suspend(() => {
|
|
150
|
+
const latch = shardLocksChanged;
|
|
151
|
+
shardLocksChanged = Latch.makeUnsafe(false);
|
|
152
|
+
return latch.open;
|
|
153
|
+
});
|
|
154
|
+
const awaitShardLock = shardId => {
|
|
155
|
+
const loop = Effect.suspend(() => isAwaitingShardLock(shardId) ? Effect.andThen(shardLocksChanged.await, loop) : Effect.void);
|
|
156
|
+
return Effect.suspend(() => isAwaitingShardLock(shardId) ? Effect.asVoid(Effect.timeoutOption(loop, config.shardLockExpiration)) : Effect.void);
|
|
157
|
+
};
|
|
134
158
|
yield* Scope.addFinalizer(shardingScope, Effect.logDebug("Shutdown complete").pipe(Effect.annotateLogs({
|
|
135
159
|
module: "effect/cluster/Sharding"
|
|
136
160
|
})));
|
|
@@ -141,6 +165,8 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
141
165
|
// This should be shutdown last, when all entities have been shutdown, to
|
|
142
166
|
// allow them to move to another runner.
|
|
143
167
|
const releasingShards = MutableHashSet.empty();
|
|
168
|
+
// Hands every held shard off on shutdown, set when this is a runner
|
|
169
|
+
let handoff = Effect.void;
|
|
144
170
|
// Shards whose entities must be force interrupted and locks fully released
|
|
145
171
|
// before normal reacquisition.
|
|
146
172
|
const forceReleasingShards = MutableHashSet.empty();
|
|
@@ -151,21 +177,40 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
151
177
|
// the locks expire over time, so if this fails we ignore it
|
|
152
178
|
return Effect.ignore(runnerStorage.releaseAll(selfAddress));
|
|
153
179
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
const
|
|
180
|
+
// Shard releases fork into a child scope that closes after the shutdown
|
|
181
|
+
// handoff, as forks into the closing sharding scope never run.
|
|
182
|
+
const releaseScope = yield* Scope.fork(shardingScope);
|
|
183
|
+
// Interrupt the shards' entities on every entity manager that is open
|
|
184
|
+
const forkInterruptShards = Effect.fnUntraced(function* (shardIds, force) {
|
|
159
185
|
const fibers = Arr.empty();
|
|
160
186
|
for (const shardId of shardIds) {
|
|
161
187
|
for (const state of entityManagers.values()) {
|
|
162
188
|
if (state.status === "closed") continue;
|
|
163
|
-
fibers.push(yield* Effect.
|
|
189
|
+
fibers.push(yield* Effect.forkIn(state.manager.interruptShard(shardId, {
|
|
164
190
|
force
|
|
165
|
-
})
|
|
191
|
+
}), releaseScope, {
|
|
192
|
+
startImmediately: true
|
|
193
|
+
}));
|
|
166
194
|
}
|
|
167
195
|
}
|
|
168
|
-
|
|
196
|
+
return fibers;
|
|
197
|
+
});
|
|
198
|
+
const releaseShardsMap = yield* FiberMap.make();
|
|
199
|
+
let forcedShardReleaseRunning = false;
|
|
200
|
+
// Called from the acquisition loop only, so no `acquire` is in flight
|
|
201
|
+
const checkHandedOff = () => {
|
|
202
|
+
if (handoffLatch.isOpen() && MutableHashSet.size(acquiredShards) === 0 && MutableHashSet.size(releasingShards) === 0 && !forcedShardReleasePending()) {
|
|
203
|
+
handedOffLatch.openUnsafe();
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
// Interrupt the shards' entities, wait for lock health, run the storage
|
|
207
|
+
// release, then clear the shards' bookkeeping.
|
|
208
|
+
const runShardRelease = Effect.fnUntraced(function* (shardIds, force, release) {
|
|
209
|
+
yield* Fiber.joinAll(yield* forkInterruptShards(shardIds, force));
|
|
210
|
+
// stop the singletons of shards that are no longer acquired, and wait
|
|
211
|
+
// for those on the released shards
|
|
212
|
+
yield* startSyncSingletons;
|
|
213
|
+
yield* awaitStoppingSingletons(shardIds);
|
|
169
214
|
yield* shardLocksHealthyLatch.await;
|
|
170
215
|
yield* release;
|
|
171
216
|
for (const shardId of shardIds) {
|
|
@@ -179,6 +224,8 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
179
224
|
interrupt: MutableRef.get(isShutdown)
|
|
180
225
|
});
|
|
181
226
|
}
|
|
227
|
+
// let the acquisition loop check whether the handoff has finished
|
|
228
|
+
if (handoffLatch.isOpen()) activeShardsLatch.openUnsafe();
|
|
182
229
|
});
|
|
183
230
|
const retryShardRelease = annotations => effect => effect.pipe(Effect.sandbox, Effect.tapError(cause => Effect.logDebug(`Could not release shards, retrying`, cause).pipe(Effect.annotateLogs({
|
|
184
231
|
module: "effect/cluster/Sharding",
|
|
@@ -212,7 +259,7 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
212
259
|
}), Effect.ensuring(Effect.sync(() => {
|
|
213
260
|
forcedShardReleaseRunning = false;
|
|
214
261
|
activeShardsLatch.openUnsafe();
|
|
215
|
-
})), Effect.forkIn(
|
|
262
|
+
})), Effect.forkIn(releaseScope), Effect.asVoid);
|
|
216
263
|
});
|
|
217
264
|
const releaseShards = Effect.gen(function* () {
|
|
218
265
|
yield* releaseForcedShards;
|
|
@@ -227,9 +274,11 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
227
274
|
while (true) {
|
|
228
275
|
yield* activeShardsLatch.await;
|
|
229
276
|
activeShardsLatch.closeUnsafe();
|
|
230
|
-
// if a shard is no longer assigned to this runner,
|
|
277
|
+
// if a shard is no longer assigned to this runner, or the runner is
|
|
278
|
+
// handing every shard off, we release it
|
|
279
|
+
const handingOff = handoffLatch.isOpen();
|
|
231
280
|
for (const shardId of acquiredShards) {
|
|
232
|
-
if (MutableHashSet.has(selfShards, shardId)) continue;
|
|
281
|
+
if (!handingOff && MutableHashSet.has(selfShards, shardId)) continue;
|
|
233
282
|
MutableHashSet.remove(acquiredShards, shardId);
|
|
234
283
|
MutableHashSet.add(releasingShards, shardId);
|
|
235
284
|
}
|
|
@@ -237,6 +286,7 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
237
286
|
yield* Effect.forkIn(syncSingletons, shardingScope);
|
|
238
287
|
yield* releaseShards;
|
|
239
288
|
}
|
|
289
|
+
checkHandedOff();
|
|
240
290
|
if (!shardLocksHealthyLatch.isOpen()) {
|
|
241
291
|
continue;
|
|
242
292
|
}
|
|
@@ -245,6 +295,10 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
245
295
|
if (forcedShardReleasePending()) {
|
|
246
296
|
continue;
|
|
247
297
|
}
|
|
298
|
+
// a runner that is shutting down takes no new shards
|
|
299
|
+
if (isShutdown.current) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
248
302
|
// if a shard has been assigned to this runner, we acquire it
|
|
249
303
|
const unacquiredShards = MutableHashSet.empty();
|
|
250
304
|
for (const shardId of selfShards) {
|
|
@@ -272,12 +326,14 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
272
326
|
MutableHashSet.add(acquiredShards, shardId);
|
|
273
327
|
}
|
|
274
328
|
if (acquired.length > 0) {
|
|
329
|
+
yield* notifyShardLockWaiters;
|
|
275
330
|
yield* storageReadLatch.open;
|
|
276
331
|
yield* Effect.forkIn(syncSingletons, shardingScope);
|
|
277
332
|
// update metrics
|
|
278
333
|
ClusterMetrics.shards.updateUnsafe(BigInt(MutableHashSet.size(acquiredShards)), Context.empty());
|
|
279
334
|
}
|
|
280
|
-
|
|
335
|
+
// the shutdown handoff releases the shards acquired here without waiting
|
|
336
|
+
yield* Effect.raceFirst(Effect.sleep(1000), handoffLatch.await);
|
|
281
337
|
activeShardsLatch.openUnsafe();
|
|
282
338
|
}
|
|
283
339
|
}).pipe(Effect.catchCause(cause => Effect.logWarning("Could not acquire/release shards", cause)), Effect.repeat(Schedule.spaced(config.entityMessagePollInterval)), Effect.annotateLogs({
|
|
@@ -297,20 +353,12 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
297
353
|
ClusterMetrics.shards.updateUnsafe(BigInt(0), Context.empty());
|
|
298
354
|
activeShardsLatch.openUnsafe();
|
|
299
355
|
return Effect.gen(function* () {
|
|
356
|
+
yield* notifyShardLockWaiters;
|
|
300
357
|
yield* Effect.logError("Shard lock storage is unhealthy", cause);
|
|
301
358
|
yield* Effect.forkIn(syncSingletons, shardingScope, {
|
|
302
359
|
startImmediately: true
|
|
303
360
|
});
|
|
304
|
-
|
|
305
|
-
for (const state of entityManagers.values()) {
|
|
306
|
-
if (state.status === "closed") continue;
|
|
307
|
-
yield* Effect.forkIn(state.manager.interruptShard(shardId, {
|
|
308
|
-
force: true
|
|
309
|
-
}), shardingScope, {
|
|
310
|
-
startImmediately: true
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
361
|
+
yield* forkInterruptShards(affectedShards, true);
|
|
314
362
|
activeShardsLatch.openUnsafe();
|
|
315
363
|
});
|
|
316
364
|
});
|
|
@@ -355,6 +403,14 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
355
403
|
yield* Effect.suspend(() => shardLocksHealthyLatch.isOpen() ? refreshShardLocks : probeShardLocks).pipe(Effect.repeat(Schedule.fixed(shardLockInterval)), Effect.forever, Effect.forkIn(shardingScope));
|
|
356
404
|
// open the shard latch every poll interval
|
|
357
405
|
yield* activeShardsLatch.open.pipe(Effect.delay(config.entityMessagePollInterval), Effect.forever, Effect.forkIn(shardingScope));
|
|
406
|
+
// Release every held shard through the per-shard path. Their entities are
|
|
407
|
+
// forced to stop, so the handoff does not wait out graceful termination.
|
|
408
|
+
// This is best effort: the wait is bounded, and if it times out, the final
|
|
409
|
+
// `releaseAll` attempts to release the remaining locks.
|
|
410
|
+
handoff = Effect.suspend(() => {
|
|
411
|
+
if (handoffLatch.openUnsafe()) activeShardsLatch.openUnsafe();
|
|
412
|
+
return forkInterruptShards([...acquiredShards, ...releasingShards], true);
|
|
413
|
+
}).pipe(Effect.andThen(Effect.timeoutOption(handedOffLatch.await, shardLockInterval)), Effect.asVoid);
|
|
358
414
|
}
|
|
359
415
|
// --- Storage inbox ---
|
|
360
416
|
//
|
|
@@ -717,28 +773,34 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
717
773
|
}), (effect, address) => Effect.ensuring(effect, Effect.sync(() => MutableHashMap.remove(entityResumptionState, address))), Effect.forkIn(shardingScope));
|
|
718
774
|
}
|
|
719
775
|
// --- Sending messages ---
|
|
720
|
-
const sendLocal = message =>
|
|
776
|
+
const sendLocal = message => {
|
|
721
777
|
const address = message.envelope.address;
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
address
|
|
734
|
-
})
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
778
|
+
const isRequest = message._tag === "IncomingRequest" || message._tag === "OutgoingRequest";
|
|
779
|
+
const send = Effect.suspend(function loop() {
|
|
780
|
+
if (!isEntityOnLocalShards(address) && (
|
|
781
|
+
// Entities draining from a released shard still accept interrupts and acks
|
|
782
|
+
isRequest || !MutableHashSet.has(releasingShards, address.shardId))) {
|
|
783
|
+
return Effect.fail(new EntityNotAssignedToRunner({
|
|
784
|
+
address
|
|
785
|
+
}));
|
|
786
|
+
}
|
|
787
|
+
const state = entityManagers.get(address.entityType);
|
|
788
|
+
if (!state) {
|
|
789
|
+
return Effect.flatMap(waitForEntityManager(address.entityType), loop);
|
|
790
|
+
} else if (state.status === "closed" || state.status === "closing" && message._tag === "IncomingRequest") {
|
|
791
|
+
// if we are shutting down, we don't accept new requests
|
|
792
|
+
return Effect.fail(new EntityNotAssignedToRunner({
|
|
793
|
+
address
|
|
794
|
+
}));
|
|
795
|
+
}
|
|
796
|
+
return message._tag === "IncomingRequest" || message._tag === "IncomingEnvelope" ? state.manager.send(message) : runnersService.sendLocal({
|
|
797
|
+
message,
|
|
798
|
+
send: state.manager.sendLocal,
|
|
799
|
+
simulateRemoteSerialization: config.simulateRemoteSerialization
|
|
800
|
+
});
|
|
740
801
|
});
|
|
741
|
-
|
|
802
|
+
return isRequest ? Effect.andThen(awaitShardLock(address.shardId), send) : send;
|
|
803
|
+
};
|
|
742
804
|
const pendingNotifications = new Map();
|
|
743
805
|
const notifyLocal = (message, discard, options) => Effect.suspend(function loop() {
|
|
744
806
|
const address = message.envelope.address;
|
|
@@ -793,7 +855,10 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
793
855
|
storageOnly: !isLocal
|
|
794
856
|
});
|
|
795
857
|
});
|
|
796
|
-
function sendOutgoing(message, discard, retries
|
|
858
|
+
function sendOutgoing(message, discard, retries,
|
|
859
|
+
// The runner serving the request, for its interrupts and acks. Later
|
|
860
|
+
// attempts fall back to the current shard assignment.
|
|
861
|
+
runner) {
|
|
797
862
|
const isPersisted = Context.get(message._tag === "OutgoingRequest" ? message.annotations : message.rpc.annotations, Persisted);
|
|
798
863
|
const shouldFail = !discard && (message._tag === "OutgoingRequest" || message.envelope._tag === "AckChunk");
|
|
799
864
|
const abandon = error => {
|
|
@@ -818,7 +883,7 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
818
883
|
address
|
|
819
884
|
}));
|
|
820
885
|
}
|
|
821
|
-
const maybeRunner = MutableHashMap.get(shardAssignments, address.shardId);
|
|
886
|
+
const maybeRunner = runner !== undefined ? Option.some(runner) : MutableHashMap.get(shardAssignments, address.shardId);
|
|
822
887
|
const runnerIsLocal = Option.isSome(maybeRunner) && isLocalRunner(maybeRunner.value);
|
|
823
888
|
if (isPersisted) {
|
|
824
889
|
return runnerIsLocal ? notifyLocal(message, discard) : runnersService.notify({
|
|
@@ -830,6 +895,9 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
830
895
|
return Effect.fail(new EntityNotAssignedToRunner({
|
|
831
896
|
address
|
|
832
897
|
}));
|
|
898
|
+
} else if (message._tag === "OutgoingRequest") {
|
|
899
|
+
const entry = clientRequests.get(message.envelope.requestId);
|
|
900
|
+
if (entry) entry.runner = maybeRunner.value;
|
|
833
901
|
}
|
|
834
902
|
return runnerIsLocal ? sendLocal(message) : discard ? runnersService.notify({
|
|
835
903
|
address: maybeRunner,
|
|
@@ -886,9 +954,12 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
886
954
|
while (true) {
|
|
887
955
|
// Ensure the current runner is registered
|
|
888
956
|
if (selfRunner && !isShutdown.current && !MutableHashMap.has(allRunners, selfRunner)) {
|
|
889
|
-
yield* Effect.
|
|
890
|
-
|
|
891
|
-
|
|
957
|
+
yield* withRegistrationLock(Effect.gen(function* () {
|
|
958
|
+
if (isShutdown.current) return;
|
|
959
|
+
yield* Effect.logDebug("Registering runner", selfRunner);
|
|
960
|
+
const machineId = yield* withTimeout(runnerStorage.register(selfRunner, true));
|
|
961
|
+
yield* snowflakeGen.setMachineId(machineId);
|
|
962
|
+
}));
|
|
892
963
|
}
|
|
893
964
|
const runners = yield* withTimeout(runnerStorage.getRunners);
|
|
894
965
|
let changed = false;
|
|
@@ -957,6 +1028,7 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
957
1028
|
});
|
|
958
1029
|
yield* Effect.logDebug("New shard assignments", selfShards);
|
|
959
1030
|
activeShardsLatch.openUnsafe();
|
|
1031
|
+
yield* notifyShardLockWaiters;
|
|
960
1032
|
// update metrics
|
|
961
1033
|
if (selfRunner) {
|
|
962
1034
|
ClusterMetrics.runnersHealthy.updateUnsafe(BigInt(MutableHashSet.has(healthyRunners, selfRunner) ? 1 : 0), Context.empty());
|
|
@@ -1040,7 +1112,7 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
1040
1112
|
replyId: entry.lastChunkId
|
|
1041
1113
|
}),
|
|
1042
1114
|
rpc: entry.rpc
|
|
1043
|
-
}), false);
|
|
1115
|
+
}), false, undefined, entry.runner);
|
|
1044
1116
|
}
|
|
1045
1117
|
case "Interrupt":
|
|
1046
1118
|
{
|
|
@@ -1066,7 +1138,7 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
1066
1138
|
requestId
|
|
1067
1139
|
}),
|
|
1068
1140
|
rpc: entry.rpc
|
|
1069
|
-
}), false, 3));
|
|
1141
|
+
}), false, 3, entry.runner));
|
|
1070
1142
|
}
|
|
1071
1143
|
}
|
|
1072
1144
|
return Effect.void;
|
|
@@ -1139,6 +1211,41 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
1139
1211
|
const singletons = new Map();
|
|
1140
1212
|
const singletonFibers = yield* FiberMap.make();
|
|
1141
1213
|
const withSingletonLock = Semaphore.makeUnsafe(1).withPermits(1);
|
|
1214
|
+
// Singletons that are stopping. They count as running, so they are not
|
|
1215
|
+
// restarted, and shard releases wait for them. Their teardown is awaited
|
|
1216
|
+
// without the singleton lock, as it can register or stop other singletons.
|
|
1217
|
+
const stoppingSingletons = MutableHashMap.empty();
|
|
1218
|
+
const isSingletonRegistered = address => {
|
|
1219
|
+
const map = singletons.get(address.shardId);
|
|
1220
|
+
return map !== undefined && MutableHashMap.has(map, address);
|
|
1221
|
+
};
|
|
1222
|
+
// Called with the singleton lock held. Starts stopping the singleton and
|
|
1223
|
+
// returns the wait for its teardown, to run without the lock.
|
|
1224
|
+
const stopSingletonUnsafe = (address, activeTeardown) => {
|
|
1225
|
+
const stopping = MutableHashMap.get(stoppingSingletons, address);
|
|
1226
|
+
if (Option.isSome(stopping)) return Fiber.join(stopping.value);
|
|
1227
|
+
const fiber = FiberMap.getUnsafe(singletonFibers, address);
|
|
1228
|
+
if (Option.isNone(fiber)) return Effect.void;
|
|
1229
|
+
const interrupt = Fiber.interrupt(fiber.value);
|
|
1230
|
+
const stop = Effect.runForkWith(services)(activeTeardown ? ActiveTeardown.aroundShard(address.shardId, interrupt) : interrupt);
|
|
1231
|
+
MutableHashMap.set(stoppingSingletons, address, stop);
|
|
1232
|
+
stop.addObserver(() => {
|
|
1233
|
+
MutableHashMap.remove(stoppingSingletons, address);
|
|
1234
|
+
// a registration made while the previous singleton was stopping
|
|
1235
|
+
if (isSingletonRegistered(address)) runFork(syncSingletons);
|
|
1236
|
+
});
|
|
1237
|
+
return Fiber.join(stop);
|
|
1238
|
+
};
|
|
1239
|
+
// Waits for the singletons of the given shards that are still stopping
|
|
1240
|
+
const awaitStoppingSingletons = shardIds => Effect.suspend(() => {
|
|
1241
|
+
const stops = Arr.empty();
|
|
1242
|
+
MutableHashMap.forEach(stoppingSingletons, (stop, address) => {
|
|
1243
|
+
if (shardIds.some(shardId => Equal.equals(shardId, address.shardId))) {
|
|
1244
|
+
stops.push(stop);
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
return Fiber.joinAll(stops);
|
|
1248
|
+
});
|
|
1142
1249
|
const registerSingleton = Effect.fnUntraced(function* (name, run, options) {
|
|
1143
1250
|
const shardGroup = options?.shardGroup ?? "default";
|
|
1144
1251
|
const address = new SingletonAddress({
|
|
@@ -1159,25 +1266,29 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
1159
1266
|
yield* PubSub.publish(events, SingletonRegistered({
|
|
1160
1267
|
address
|
|
1161
1268
|
}));
|
|
1162
|
-
// start if we are on the right shard
|
|
1163
|
-
|
|
1269
|
+
// start if we are on the right shard, unless a previous registration of
|
|
1270
|
+
// this singleton is still stopping
|
|
1271
|
+
if (MutableHashSet.has(acquiredShards, address.shardId) && !MutableHashMap.has(stoppingSingletons, address)) {
|
|
1164
1272
|
yield* Effect.logDebug("Starting singleton", address);
|
|
1165
1273
|
yield* FiberMap.run(singletonFibers, address, wrappedRun);
|
|
1166
1274
|
}
|
|
1167
|
-
yield* Effect.addFinalizer(() => {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
});
|
|
1275
|
+
yield* Effect.addFinalizer(() => withSingletonLock(Effect.sync(() => {
|
|
1276
|
+
MutableHashMap.remove(singletons.get(address.shardId), address);
|
|
1277
|
+
return stopSingletonUnsafe(address, false);
|
|
1278
|
+
})).pipe(Effect.flatten));
|
|
1172
1279
|
}, withSingletonLock);
|
|
1173
|
-
|
|
1280
|
+
// Starts and stops singletons to match the acquired shards, returning the
|
|
1281
|
+
// waits for the stopped singletons
|
|
1282
|
+
const startSyncSingletons = withSingletonLock(Effect.gen(function* () {
|
|
1283
|
+
const stops = Arr.empty();
|
|
1174
1284
|
for (const [shardId, map] of singletons) {
|
|
1175
1285
|
for (const [address, run] of map) {
|
|
1176
|
-
const
|
|
1286
|
+
const stopping = MutableHashMap.has(stoppingSingletons, address);
|
|
1287
|
+
const running = stopping || FiberMap.hasUnsafe(singletonFibers, address);
|
|
1177
1288
|
const shouldBeRunning = MutableHashSet.has(acquiredShards, shardId);
|
|
1178
1289
|
if (running && !shouldBeRunning) {
|
|
1179
|
-
yield* Effect.logDebug("Stopping singleton", address);
|
|
1180
|
-
|
|
1290
|
+
if (!stopping) yield* Effect.logDebug("Stopping singleton", address);
|
|
1291
|
+
stops.push(stopSingletonUnsafe(address, true));
|
|
1181
1292
|
} else if (!running && shouldBeRunning) {
|
|
1182
1293
|
yield* Effect.logDebug("Starting singleton", address);
|
|
1183
1294
|
yield* FiberMap.run(singletonFibers, address, run);
|
|
@@ -1185,6 +1296,12 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
1185
1296
|
}
|
|
1186
1297
|
}
|
|
1187
1298
|
ClusterMetrics.singletons.updateUnsafe(BigInt(yield* FiberMap.size(singletonFibers)), Context.empty());
|
|
1299
|
+
return stops;
|
|
1300
|
+
}));
|
|
1301
|
+
const syncSingletons = Effect.flatMap(startSyncSingletons,
|
|
1302
|
+
// wait for the stopped singletons without the lock
|
|
1303
|
+
stops => Effect.all(stops, {
|
|
1304
|
+
discard: true
|
|
1188
1305
|
}));
|
|
1189
1306
|
// --- Entities ---
|
|
1190
1307
|
const reaper = yield* EntityReaper;
|
|
@@ -1283,11 +1400,13 @@ const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
1283
1400
|
}
|
|
1284
1401
|
if (isShutdown.current) return;
|
|
1285
1402
|
MutableRef.set(isShutdown, true);
|
|
1403
|
+
yield* notifyShardLockWaiters;
|
|
1286
1404
|
if (selfRunner) {
|
|
1287
|
-
yield* Effect.ignore(runnerStorage.unregister(selfRunner.address));
|
|
1405
|
+
yield* Effect.ignore(withRegistrationLock(runnerStorage.unregister(selfRunner.address)));
|
|
1288
1406
|
}
|
|
1289
1407
|
});
|
|
1290
|
-
|
|
1408
|
+
// hand the shards off before the loops stop
|
|
1409
|
+
yield* Scope.addFinalizerExit(shardingScope, exit => Effect.andThen(shutdown(exit), handoff));
|
|
1291
1410
|
const activeEntityCount = Effect.gen(function* () {
|
|
1292
1411
|
let count = 0;
|
|
1293
1412
|
for (const state of entityManagers.values()) {
|