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
|
@@ -207,7 +207,12 @@ export class MessageStorage extends Context.Service<MessageStorage, {
|
|
|
207
207
|
) => Effect.Effect<void, PersistenceError>
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
|
-
*
|
|
210
|
+
* Wraps requests in storage transactions.
|
|
211
|
+
*
|
|
212
|
+
* `WithExit` replies saved in a transaction notify callers after the outermost
|
|
213
|
+
* commit; stream chunks notify immediately. Replies saved in a failed
|
|
214
|
+
* transaction must be discarded. The wrapper may fail only with the wrapped
|
|
215
|
+
* effect's own failure or a defect.
|
|
211
216
|
*/
|
|
212
217
|
readonly withTransaction: <A, E, R>(
|
|
213
218
|
effect: Effect.Effect<A, E, R>
|
|
@@ -513,6 +518,10 @@ export type EncodedRepliesOptions<A> = {
|
|
|
513
518
|
readonly cursor: Option.Option<A>
|
|
514
519
|
}
|
|
515
520
|
|
|
521
|
+
const TransactionNotifications = Context.Reference<
|
|
522
|
+
Array<Effect.Effect<void, PersistenceError | MalformedMessage>> | undefined
|
|
523
|
+
>("effect/cluster/MessageStorage/TransactionNotifications", { defaultValue: () => undefined })
|
|
524
|
+
|
|
516
525
|
/**
|
|
517
526
|
* Wraps a concrete message storage implementation with reply-handler management.
|
|
518
527
|
*
|
|
@@ -555,6 +564,20 @@ export const make = (
|
|
|
555
564
|
const replyHandlersShard = new Map<string, Set<ReplyHandler>>()
|
|
556
565
|
return MessageStorage.of({
|
|
557
566
|
...storage,
|
|
567
|
+
// Nested transactions defer notifications to the outermost commit.
|
|
568
|
+
withTransaction: (effect) =>
|
|
569
|
+
TransactionNotifications.use((outer) => {
|
|
570
|
+
const notifications: Array<Effect.Effect<void, PersistenceError | MalformedMessage>> = []
|
|
571
|
+
return storage.withTransaction(Effect.provideService(effect, TransactionNotifications, notifications)).pipe(
|
|
572
|
+
Effect.tap(() =>
|
|
573
|
+
outer
|
|
574
|
+
? Effect.sync(() => {
|
|
575
|
+
outer.push(...notifications)
|
|
576
|
+
})
|
|
577
|
+
: Effect.orDie(Effect.forEach(notifications, identity, { discard: true }))
|
|
578
|
+
)
|
|
579
|
+
)
|
|
580
|
+
}),
|
|
558
581
|
registerReplyHandler: (message) => {
|
|
559
582
|
const requestId = message.envelope.requestId
|
|
560
583
|
return Effect.callback<void, EntityNotAssignedToRunner>((resume) => {
|
|
@@ -619,20 +642,28 @@ export const make = (
|
|
|
619
642
|
saveReply(reply) {
|
|
620
643
|
const requestId = reply.reply.requestId
|
|
621
644
|
return Effect.flatMap(storage.saveReply(reply), (persisted) => {
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
645
|
+
const notify = Effect.suspend(() => {
|
|
646
|
+
const handlers = replyHandlers.get(requestId)
|
|
647
|
+
if (!handlers) {
|
|
648
|
+
return Effect.void
|
|
649
|
+
} else if (persisted.reply._tag === "WithExit") {
|
|
650
|
+
replyHandlers.delete(requestId)
|
|
651
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
652
|
+
const handler = handlers[i]
|
|
653
|
+
handler.shardSet.delete(handler)
|
|
654
|
+
handler.resume(Effect.void)
|
|
655
|
+
}
|
|
631
656
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
657
|
+
return handlers.length === 1
|
|
658
|
+
? handlers[0].respond(persisted)
|
|
659
|
+
: Effect.forEach(handlers, (handler) => handler.respond(persisted), { discard: true })
|
|
660
|
+
})
|
|
661
|
+
if (persisted.reply._tag !== "WithExit") return notify
|
|
662
|
+
return TransactionNotifications.use((notifications) =>
|
|
663
|
+
notifications === undefined ? notify : Effect.sync(() => {
|
|
664
|
+
notifications.push(notify)
|
|
665
|
+
})
|
|
666
|
+
)
|
|
636
667
|
})
|
|
637
668
|
}
|
|
638
669
|
})
|
package/src/cluster/Runners.ts
CHANGED
|
@@ -282,6 +282,10 @@ export const make: (
|
|
|
282
282
|
const reply = entry.replies[i]
|
|
283
283
|
// we have reached the end
|
|
284
284
|
if (reply._tag === "WithExit") {
|
|
285
|
+
// A reply can immediately reset and resend the same request. Do not
|
|
286
|
+
// let that sender attach to this completed reply loop.
|
|
287
|
+
storageRequests.delete(message.envelope.requestId)
|
|
288
|
+
waitingStorageRequests.delete(message.envelope.requestId)
|
|
285
289
|
for (const message of entry.messages) {
|
|
286
290
|
yield* message.respond(reply)
|
|
287
291
|
}
|
|
@@ -304,8 +308,10 @@ export const make: (
|
|
|
304
308
|
effect,
|
|
305
309
|
Effect.sync(() => {
|
|
306
310
|
const entry = storageRequests.get(message.envelope.requestId)
|
|
307
|
-
|
|
308
|
-
|
|
311
|
+
// An old waiter must not clean up a reset request's new reply loop.
|
|
312
|
+
if (!entry?.messages.has(message)) return
|
|
313
|
+
if (entry.messages.size > 1) {
|
|
314
|
+
entry.messages.delete(message)
|
|
309
315
|
return
|
|
310
316
|
}
|
|
311
317
|
storageRequests.delete(message.envelope.requestId)
|
package/src/cluster/Sharding.ts
CHANGED
|
@@ -277,6 +277,13 @@ const make = Effect.gen(function*() {
|
|
|
277
277
|
// the active shards are the ones that we have acquired the lock for
|
|
278
278
|
const acquiredShards = MutableHashSet.empty<ShardId>()
|
|
279
279
|
const activeShardsLatch = yield* Latch.make(false)
|
|
280
|
+
// open once shutdown starts handing every shard off
|
|
281
|
+
const handoffLatch = Latch.makeUnsafe(false)
|
|
282
|
+
// open once the handoff has released every shard lock
|
|
283
|
+
const handedOffLatch = Latch.makeUnsafe(false)
|
|
284
|
+
// serializes runner registration with unregistration, so a registration
|
|
285
|
+
// in flight cannot re-advertise a runner that is shutting down
|
|
286
|
+
const withRegistrationLock = Semaphore.makeUnsafe(1).withPermits(1)
|
|
280
287
|
|
|
281
288
|
const events = yield* PubSub.unbounded<ShardingRegistrationEvent>()
|
|
282
289
|
const getRegistrationEvents: Stream.Stream<ShardingRegistrationEvent> = Stream.fromPubSub(events)
|
|
@@ -295,6 +302,33 @@ const make = Effect.gen(function*() {
|
|
|
295
302
|
return MutableHashSet.has(acquiredShards, address.shardId)
|
|
296
303
|
}
|
|
297
304
|
|
|
305
|
+
function isAwaitingShardLock(shardId: ShardId): boolean {
|
|
306
|
+
return !MutableRef.get(isShutdown) &&
|
|
307
|
+
MutableHashSet.has(selfShards, shardId) &&
|
|
308
|
+
!MutableHashSet.has(acquiredShards, shardId)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Requests for a shard assigned to this runner wait for its lock, for at most
|
|
312
|
+
// `shardLockExpiration`, instead of bouncing while the previous owner drains.
|
|
313
|
+
// Each notification opens the current latch and replaces it, so a waiter that
|
|
314
|
+
// checked its shard before a notification cannot miss it.
|
|
315
|
+
let shardLocksChanged = Latch.makeUnsafe(false)
|
|
316
|
+
const notifyShardLockWaiters = Effect.suspend(() => {
|
|
317
|
+
const latch = shardLocksChanged
|
|
318
|
+
shardLocksChanged = Latch.makeUnsafe(false)
|
|
319
|
+
return latch.open
|
|
320
|
+
})
|
|
321
|
+
const awaitShardLock = (shardId: ShardId): Effect.Effect<void> => {
|
|
322
|
+
const loop: Effect.Effect<void> = Effect.suspend(() =>
|
|
323
|
+
isAwaitingShardLock(shardId) ? Effect.andThen(shardLocksChanged.await, loop) : Effect.void
|
|
324
|
+
)
|
|
325
|
+
return Effect.suspend(() =>
|
|
326
|
+
isAwaitingShardLock(shardId)
|
|
327
|
+
? Effect.asVoid(Effect.timeoutOption(loop, config.shardLockExpiration))
|
|
328
|
+
: Effect.void
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
|
|
298
332
|
yield* Scope.addFinalizer(
|
|
299
333
|
shardingScope,
|
|
300
334
|
Effect.logDebug("Shutdown complete").pipe(Effect.annotateLogs({
|
|
@@ -310,6 +344,8 @@ const make = Effect.gen(function*() {
|
|
|
310
344
|
// allow them to move to another runner.
|
|
311
345
|
|
|
312
346
|
const releasingShards = MutableHashSet.empty<ShardId>()
|
|
347
|
+
// Hands every held shard off on shutdown, set when this is a runner
|
|
348
|
+
let handoff: Effect.Effect<void> = Effect.void
|
|
313
349
|
// Shards whose entities must be force interrupted and locks fully released
|
|
314
350
|
// before normal reacquisition.
|
|
315
351
|
const forceReleasingShards = MutableHashSet.empty<ShardId>()
|
|
@@ -321,8 +357,37 @@ const make = Effect.gen(function*() {
|
|
|
321
357
|
return Effect.ignore(runnerStorage.releaseAll(selfAddress))
|
|
322
358
|
})
|
|
323
359
|
|
|
360
|
+
// Shard releases fork into a child scope that closes after the shutdown
|
|
361
|
+
// handoff, as forks into the closing sharding scope never run.
|
|
362
|
+
const releaseScope = yield* Scope.fork(shardingScope)
|
|
363
|
+
// Interrupt the shards' entities on every entity manager that is open
|
|
364
|
+
const forkInterruptShards = Effect.fnUntraced(function*(shardIds: Iterable<ShardId>, force: boolean) {
|
|
365
|
+
const fibers = Arr.empty<Fiber.Fiber<void>>()
|
|
366
|
+
for (const shardId of shardIds) {
|
|
367
|
+
for (const state of entityManagers.values()) {
|
|
368
|
+
if (state.status === "closed") continue
|
|
369
|
+
fibers.push(
|
|
370
|
+
yield* Effect.forkIn(state.manager.interruptShard(shardId, { force }), releaseScope, {
|
|
371
|
+
startImmediately: true
|
|
372
|
+
})
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return fibers
|
|
377
|
+
})
|
|
324
378
|
const releaseShardsMap = yield* FiberMap.make<ShardId>()
|
|
325
379
|
let forcedShardReleaseRunning = false
|
|
380
|
+
// Called from the acquisition loop only, so no `acquire` is in flight
|
|
381
|
+
const checkHandedOff = () => {
|
|
382
|
+
if (
|
|
383
|
+
handoffLatch.isOpen() &&
|
|
384
|
+
MutableHashSet.size(acquiredShards) === 0 &&
|
|
385
|
+
MutableHashSet.size(releasingShards) === 0 &&
|
|
386
|
+
!forcedShardReleasePending()
|
|
387
|
+
) {
|
|
388
|
+
handedOffLatch.openUnsafe()
|
|
389
|
+
}
|
|
390
|
+
}
|
|
326
391
|
// Interrupt the shards' entities, wait for lock health, run the storage
|
|
327
392
|
// release, then clear the shards' bookkeeping.
|
|
328
393
|
const runShardRelease = Effect.fnUntraced(function*<E>(
|
|
@@ -330,14 +395,11 @@ const make = Effect.gen(function*() {
|
|
|
330
395
|
force: boolean,
|
|
331
396
|
release: Effect.Effect<void, E>
|
|
332
397
|
) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
yield* Fiber.joinAll(fibers)
|
|
398
|
+
yield* Fiber.joinAll(yield* forkInterruptShards(shardIds, force))
|
|
399
|
+
// stop the singletons of shards that are no longer acquired, and wait
|
|
400
|
+
// for those on the released shards
|
|
401
|
+
yield* startSyncSingletons
|
|
402
|
+
yield* awaitStoppingSingletons(shardIds)
|
|
341
403
|
yield* shardLocksHealthyLatch.await
|
|
342
404
|
yield* release
|
|
343
405
|
for (const shardId of shardIds) {
|
|
@@ -351,6 +413,8 @@ const make = Effect.gen(function*() {
|
|
|
351
413
|
interrupt: MutableRef.get(isShutdown)
|
|
352
414
|
})
|
|
353
415
|
}
|
|
416
|
+
// let the acquisition loop check whether the handoff has finished
|
|
417
|
+
if (handoffLatch.isOpen()) activeShardsLatch.openUnsafe()
|
|
354
418
|
})
|
|
355
419
|
const retryShardRelease =
|
|
356
420
|
(annotations: { readonly fiber: string; readonly shardId?: ShardId }) =>
|
|
@@ -402,7 +466,7 @@ const make = Effect.gen(function*() {
|
|
|
402
466
|
forcedShardReleaseRunning = false
|
|
403
467
|
activeShardsLatch.openUnsafe()
|
|
404
468
|
})),
|
|
405
|
-
Effect.forkIn(
|
|
469
|
+
Effect.forkIn(releaseScope),
|
|
406
470
|
Effect.asVoid
|
|
407
471
|
)
|
|
408
472
|
})
|
|
@@ -422,9 +486,11 @@ const make = Effect.gen(function*() {
|
|
|
422
486
|
yield* activeShardsLatch.await
|
|
423
487
|
activeShardsLatch.closeUnsafe()
|
|
424
488
|
|
|
425
|
-
// if a shard is no longer assigned to this runner,
|
|
489
|
+
// if a shard is no longer assigned to this runner, or the runner is
|
|
490
|
+
// handing every shard off, we release it
|
|
491
|
+
const handingOff = handoffLatch.isOpen()
|
|
426
492
|
for (const shardId of acquiredShards) {
|
|
427
|
-
if (MutableHashSet.has(selfShards, shardId)) continue
|
|
493
|
+
if (!handingOff && MutableHashSet.has(selfShards, shardId)) continue
|
|
428
494
|
MutableHashSet.remove(acquiredShards, shardId)
|
|
429
495
|
MutableHashSet.add(releasingShards, shardId)
|
|
430
496
|
}
|
|
@@ -433,6 +499,7 @@ const make = Effect.gen(function*() {
|
|
|
433
499
|
yield* Effect.forkIn(syncSingletons, shardingScope)
|
|
434
500
|
yield* releaseShards
|
|
435
501
|
}
|
|
502
|
+
checkHandedOff()
|
|
436
503
|
|
|
437
504
|
if (!shardLocksHealthyLatch.isOpen()) {
|
|
438
505
|
continue
|
|
@@ -444,6 +511,11 @@ const make = Effect.gen(function*() {
|
|
|
444
511
|
continue
|
|
445
512
|
}
|
|
446
513
|
|
|
514
|
+
// a runner that is shutting down takes no new shards
|
|
515
|
+
if (isShutdown.current) {
|
|
516
|
+
continue
|
|
517
|
+
}
|
|
518
|
+
|
|
447
519
|
// if a shard has been assigned to this runner, we acquire it
|
|
448
520
|
const unacquiredShards = MutableHashSet.empty<ShardId>()
|
|
449
521
|
for (const shardId of selfShards) {
|
|
@@ -484,13 +556,15 @@ const make = Effect.gen(function*() {
|
|
|
484
556
|
MutableHashSet.add(acquiredShards, shardId)
|
|
485
557
|
}
|
|
486
558
|
if (acquired.length > 0) {
|
|
559
|
+
yield* notifyShardLockWaiters
|
|
487
560
|
yield* storageReadLatch.open
|
|
488
561
|
yield* Effect.forkIn(syncSingletons, shardingScope)
|
|
489
562
|
|
|
490
563
|
// update metrics
|
|
491
564
|
ClusterMetrics.shards.updateUnsafe(BigInt(MutableHashSet.size(acquiredShards)), Context.empty())
|
|
492
565
|
}
|
|
493
|
-
|
|
566
|
+
// the shutdown handoff releases the shards acquired here without waiting
|
|
567
|
+
yield* Effect.raceFirst(Effect.sleep(1000), handoffLatch.await)
|
|
494
568
|
activeShardsLatch.openUnsafe()
|
|
495
569
|
}
|
|
496
570
|
}).pipe(
|
|
@@ -519,19 +593,11 @@ const make = Effect.gen(function*() {
|
|
|
519
593
|
activeShardsLatch.openUnsafe()
|
|
520
594
|
|
|
521
595
|
return Effect.gen(function*() {
|
|
596
|
+
yield* notifyShardLockWaiters
|
|
522
597
|
yield* Effect.logError("Shard lock storage is unhealthy", cause)
|
|
523
598
|
yield* Effect.forkIn(syncSingletons, shardingScope, { startImmediately: true })
|
|
524
599
|
|
|
525
|
-
|
|
526
|
-
for (const state of entityManagers.values()) {
|
|
527
|
-
if (state.status === "closed") continue
|
|
528
|
-
yield* Effect.forkIn(
|
|
529
|
-
state.manager.interruptShard(shardId, { force: true }),
|
|
530
|
-
shardingScope,
|
|
531
|
-
{ startImmediately: true }
|
|
532
|
-
)
|
|
533
|
-
}
|
|
534
|
-
}
|
|
600
|
+
yield* forkInterruptShards(affectedShards, true)
|
|
535
601
|
activeShardsLatch.openUnsafe()
|
|
536
602
|
})
|
|
537
603
|
})
|
|
@@ -598,6 +664,18 @@ const make = Effect.gen(function*() {
|
|
|
598
664
|
Effect.forever,
|
|
599
665
|
Effect.forkIn(shardingScope)
|
|
600
666
|
)
|
|
667
|
+
|
|
668
|
+
// Release every held shard through the per-shard path. Their entities are
|
|
669
|
+
// forced to stop, so the handoff does not wait out graceful termination.
|
|
670
|
+
// This is best effort: the wait is bounded, and if it times out, the final
|
|
671
|
+
// `releaseAll` attempts to release the remaining locks.
|
|
672
|
+
handoff = Effect.suspend(() => {
|
|
673
|
+
if (handoffLatch.openUnsafe()) activeShardsLatch.openUnsafe()
|
|
674
|
+
return forkInterruptShards([...acquiredShards, ...releasingShards], true)
|
|
675
|
+
}).pipe(
|
|
676
|
+
Effect.andThen(Effect.timeoutOption(handedOffLatch.await, shardLockInterval)),
|
|
677
|
+
Effect.asVoid
|
|
678
|
+
)
|
|
601
679
|
}
|
|
602
680
|
|
|
603
681
|
// --- Storage inbox ---
|
|
@@ -1036,16 +1114,21 @@ const make = Effect.gen(function*() {
|
|
|
1036
1114
|
|
|
1037
1115
|
// --- Sending messages ---
|
|
1038
1116
|
|
|
1039
|
-
const sendLocal = <M extends Message.Outgoing<any> | Message.Incoming<any>>(message: M) =>
|
|
1040
|
-
|
|
1117
|
+
const sendLocal = <M extends Message.Outgoing<any> | Message.Incoming<any>>(message: M) => {
|
|
1118
|
+
const address = message.envelope.address
|
|
1119
|
+
const isRequest = message._tag === "IncomingRequest" || message._tag === "OutgoingRequest"
|
|
1120
|
+
const send = Effect.suspend(function loop(): Effect.Effect<
|
|
1041
1121
|
void,
|
|
1042
1122
|
| EntityNotAssignedToRunner
|
|
1043
1123
|
| MailboxFull
|
|
1044
1124
|
| AlreadyProcessingMessage
|
|
1045
1125
|
| (M extends Message.Incoming<any> ? never : PersistenceError)
|
|
1046
1126
|
> {
|
|
1047
|
-
|
|
1048
|
-
|
|
1127
|
+
if (
|
|
1128
|
+
!isEntityOnLocalShards(address) &&
|
|
1129
|
+
// Entities draining from a released shard still accept interrupts and acks
|
|
1130
|
+
(isRequest || !MutableHashSet.has(releasingShards, address.shardId))
|
|
1131
|
+
) {
|
|
1049
1132
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
|
1050
1133
|
}
|
|
1051
1134
|
const state = entityManagers.get(address.entityType)
|
|
@@ -1064,6 +1147,8 @@ const make = Effect.gen(function*() {
|
|
|
1064
1147
|
simulateRemoteSerialization: config.simulateRemoteSerialization
|
|
1065
1148
|
}) as any
|
|
1066
1149
|
})
|
|
1150
|
+
return isRequest ? Effect.andThen(awaitShardLock(address.shardId), send) : send
|
|
1151
|
+
}
|
|
1067
1152
|
|
|
1068
1153
|
type PendingNotification = {
|
|
1069
1154
|
resume: (_: Effect.Effect<void, EntityNotAssignedToRunner>) => void
|
|
@@ -1129,7 +1214,10 @@ const make = Effect.gen(function*() {
|
|
|
1129
1214
|
function sendOutgoing(
|
|
1130
1215
|
message: Message.Outgoing<any>,
|
|
1131
1216
|
discard: boolean,
|
|
1132
|
-
retries?: number
|
|
1217
|
+
retries?: number,
|
|
1218
|
+
// The runner serving the request, for its interrupts and acks. Later
|
|
1219
|
+
// attempts fall back to the current shard assignment.
|
|
1220
|
+
runner?: RunnerAddress
|
|
1133
1221
|
): Effect.Effect<
|
|
1134
1222
|
void,
|
|
1135
1223
|
MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
|
|
@@ -1172,7 +1260,9 @@ const make = Effect.gen(function*() {
|
|
|
1172
1260
|
if (shouldFail && MutableRef.get(isShutdown)) {
|
|
1173
1261
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
|
1174
1262
|
}
|
|
1175
|
-
const maybeRunner =
|
|
1263
|
+
const maybeRunner = runner !== undefined
|
|
1264
|
+
? Option.some(runner)
|
|
1265
|
+
: MutableHashMap.get(shardAssignments, address.shardId)
|
|
1176
1266
|
const runnerIsLocal = Option.isSome(maybeRunner) && isLocalRunner(maybeRunner.value)
|
|
1177
1267
|
if (isPersisted) {
|
|
1178
1268
|
return runnerIsLocal
|
|
@@ -1180,6 +1270,9 @@ const make = Effect.gen(function*() {
|
|
|
1180
1270
|
: runnersService.notify({ address: maybeRunner, message, discard })
|
|
1181
1271
|
} else if (Option.isNone(maybeRunner)) {
|
|
1182
1272
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
|
1273
|
+
} else if (message._tag === "OutgoingRequest") {
|
|
1274
|
+
const entry = clientRequests.get(message.envelope.requestId)
|
|
1275
|
+
if (entry) entry.runner = maybeRunner.value
|
|
1183
1276
|
}
|
|
1184
1277
|
return runnerIsLocal
|
|
1185
1278
|
? sendLocal(message)
|
|
@@ -1250,9 +1343,12 @@ const make = Effect.gen(function*() {
|
|
|
1250
1343
|
while (true) {
|
|
1251
1344
|
// Ensure the current runner is registered
|
|
1252
1345
|
if (selfRunner && !isShutdown.current && !MutableHashMap.has(allRunners, selfRunner)) {
|
|
1253
|
-
yield* Effect.
|
|
1254
|
-
|
|
1255
|
-
|
|
1346
|
+
yield* withRegistrationLock(Effect.gen(function*() {
|
|
1347
|
+
if (isShutdown.current) return
|
|
1348
|
+
yield* Effect.logDebug("Registering runner", selfRunner)
|
|
1349
|
+
const machineId = yield* withTimeout(runnerStorage.register(selfRunner, true))
|
|
1350
|
+
yield* snowflakeGen.setMachineId(machineId)
|
|
1351
|
+
}))
|
|
1256
1352
|
}
|
|
1257
1353
|
|
|
1258
1354
|
const runners = yield* withTimeout(runnerStorage.getRunners)
|
|
@@ -1324,6 +1420,7 @@ const make = Effect.gen(function*() {
|
|
|
1324
1420
|
})
|
|
1325
1421
|
yield* Effect.logDebug("New shard assignments", selfShards)
|
|
1326
1422
|
activeShardsLatch.openUnsafe()
|
|
1423
|
+
yield* notifyShardLockWaiters
|
|
1327
1424
|
|
|
1328
1425
|
// update metrics
|
|
1329
1426
|
if (selfRunner) {
|
|
@@ -1361,6 +1458,8 @@ const make = Effect.gen(function*() {
|
|
|
1361
1458
|
readonly context: Context.Context<never>
|
|
1362
1459
|
readonly message: Message.OutgoingRequest<any>
|
|
1363
1460
|
lastChunkId?: Snowflake.Snowflake
|
|
1461
|
+
// the runner a volatile request was last sent to
|
|
1462
|
+
runner?: RunnerAddress
|
|
1364
1463
|
}
|
|
1365
1464
|
const clientRequests = new Map<Snowflake.Snowflake, ClientRequestEntry>()
|
|
1366
1465
|
|
|
@@ -1445,7 +1544,9 @@ const make = Effect.gen(function*() {
|
|
|
1445
1544
|
}),
|
|
1446
1545
|
rpc: entry.rpc
|
|
1447
1546
|
}),
|
|
1448
|
-
false
|
|
1547
|
+
false,
|
|
1548
|
+
undefined,
|
|
1549
|
+
entry.runner
|
|
1449
1550
|
)
|
|
1450
1551
|
}
|
|
1451
1552
|
case "Interrupt": {
|
|
@@ -1476,7 +1577,8 @@ const make = Effect.gen(function*() {
|
|
|
1476
1577
|
rpc: entry.rpc
|
|
1477
1578
|
}),
|
|
1478
1579
|
false,
|
|
1479
|
-
3
|
|
1580
|
+
3,
|
|
1581
|
+
entry.runner
|
|
1480
1582
|
))
|
|
1481
1583
|
}
|
|
1482
1584
|
}
|
|
@@ -1569,6 +1671,47 @@ const make = Effect.gen(function*() {
|
|
|
1569
1671
|
const singletonFibers = yield* FiberMap.make<SingletonAddress>()
|
|
1570
1672
|
const withSingletonLock = Semaphore.makeUnsafe(1).withPermits(1)
|
|
1571
1673
|
|
|
1674
|
+
// Singletons that are stopping. They count as running, so they are not
|
|
1675
|
+
// restarted, and shard releases wait for them. Their teardown is awaited
|
|
1676
|
+
// without the singleton lock, as it can register or stop other singletons.
|
|
1677
|
+
const stoppingSingletons = MutableHashMap.empty<SingletonAddress, Fiber.Fiber<void>>()
|
|
1678
|
+
const isSingletonRegistered = (address: SingletonAddress) => {
|
|
1679
|
+
const map = singletons.get(address.shardId)
|
|
1680
|
+
return map !== undefined && MutableHashMap.has(map, address)
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// Called with the singleton lock held. Starts stopping the singleton and
|
|
1684
|
+
// returns the wait for its teardown, to run without the lock.
|
|
1685
|
+
const stopSingletonUnsafe = (address: SingletonAddress, activeTeardown: boolean): Effect.Effect<void> => {
|
|
1686
|
+
const stopping = MutableHashMap.get(stoppingSingletons, address)
|
|
1687
|
+
if (Option.isSome(stopping)) return Fiber.join(stopping.value)
|
|
1688
|
+
const fiber = FiberMap.getUnsafe(singletonFibers, address)
|
|
1689
|
+
if (Option.isNone(fiber)) return Effect.void
|
|
1690
|
+
const interrupt = Fiber.interrupt(fiber.value)
|
|
1691
|
+
const stop = Effect.runForkWith(services)(
|
|
1692
|
+
activeTeardown ? ActiveTeardown.aroundShard(address.shardId, interrupt) : interrupt
|
|
1693
|
+
)
|
|
1694
|
+
MutableHashMap.set(stoppingSingletons, address, stop)
|
|
1695
|
+
stop.addObserver(() => {
|
|
1696
|
+
MutableHashMap.remove(stoppingSingletons, address)
|
|
1697
|
+
// a registration made while the previous singleton was stopping
|
|
1698
|
+
if (isSingletonRegistered(address)) runFork(syncSingletons)
|
|
1699
|
+
})
|
|
1700
|
+
return Fiber.join(stop)
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// Waits for the singletons of the given shards that are still stopping
|
|
1704
|
+
const awaitStoppingSingletons = (shardIds: ReadonlyArray<ShardId>) =>
|
|
1705
|
+
Effect.suspend(() => {
|
|
1706
|
+
const stops = Arr.empty<Fiber.Fiber<void>>()
|
|
1707
|
+
MutableHashMap.forEach(stoppingSingletons, (stop, address) => {
|
|
1708
|
+
if (shardIds.some((shardId) => Equal.equals(shardId, address.shardId))) {
|
|
1709
|
+
stops.push(stop)
|
|
1710
|
+
}
|
|
1711
|
+
})
|
|
1712
|
+
return Fiber.joinAll(stops)
|
|
1713
|
+
})
|
|
1714
|
+
|
|
1572
1715
|
const registerSingleton: Sharding["Service"]["registerSingleton"] = Effect.fnUntraced(
|
|
1573
1716
|
function*(name, run, options) {
|
|
1574
1717
|
const shardGroup = options?.shardGroup ?? "default"
|
|
@@ -1599,32 +1742,38 @@ const make = Effect.gen(function*() {
|
|
|
1599
1742
|
|
|
1600
1743
|
yield* PubSub.publish(events, SingletonRegistered({ address }))
|
|
1601
1744
|
|
|
1602
|
-
// start if we are on the right shard
|
|
1603
|
-
|
|
1745
|
+
// start if we are on the right shard, unless a previous registration of
|
|
1746
|
+
// this singleton is still stopping
|
|
1747
|
+
if (
|
|
1748
|
+
MutableHashSet.has(acquiredShards, address.shardId) &&
|
|
1749
|
+
!MutableHashMap.has(stoppingSingletons, address)
|
|
1750
|
+
) {
|
|
1604
1751
|
yield* Effect.logDebug("Starting singleton", address)
|
|
1605
1752
|
yield* FiberMap.run(singletonFibers, address, wrappedRun)
|
|
1606
1753
|
}
|
|
1607
1754
|
|
|
1608
|
-
yield* Effect.addFinalizer(() =>
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1755
|
+
yield* Effect.addFinalizer(() =>
|
|
1756
|
+
withSingletonLock(Effect.sync(() => {
|
|
1757
|
+
MutableHashMap.remove(singletons.get(address.shardId)!, address)
|
|
1758
|
+
return stopSingletonUnsafe(address, false)
|
|
1759
|
+
})).pipe(Effect.flatten)
|
|
1760
|
+
)
|
|
1613
1761
|
},
|
|
1614
1762
|
withSingletonLock
|
|
1615
1763
|
)
|
|
1616
1764
|
|
|
1617
|
-
|
|
1765
|
+
// Starts and stops singletons to match the acquired shards, returning the
|
|
1766
|
+
// waits for the stopped singletons
|
|
1767
|
+
const startSyncSingletons = withSingletonLock(Effect.gen(function*() {
|
|
1768
|
+
const stops = Arr.empty<Effect.Effect<void>>()
|
|
1618
1769
|
for (const [shardId, map] of singletons) {
|
|
1619
1770
|
for (const [address, run] of map) {
|
|
1620
|
-
const
|
|
1771
|
+
const stopping = MutableHashMap.has(stoppingSingletons, address)
|
|
1772
|
+
const running = stopping || FiberMap.hasUnsafe(singletonFibers, address)
|
|
1621
1773
|
const shouldBeRunning = MutableHashSet.has(acquiredShards, shardId)
|
|
1622
1774
|
if (running && !shouldBeRunning) {
|
|
1623
|
-
yield* Effect.logDebug("Stopping singleton", address)
|
|
1624
|
-
|
|
1625
|
-
address.shardId,
|
|
1626
|
-
FiberMap.remove(singletonFibers, address)
|
|
1627
|
-
)
|
|
1775
|
+
if (!stopping) yield* Effect.logDebug("Stopping singleton", address)
|
|
1776
|
+
stops.push(stopSingletonUnsafe(address, true))
|
|
1628
1777
|
} else if (!running && shouldBeRunning) {
|
|
1629
1778
|
yield* Effect.logDebug("Starting singleton", address)
|
|
1630
1779
|
yield* FiberMap.run(singletonFibers, address, run)
|
|
@@ -1635,8 +1784,15 @@ const make = Effect.gen(function*() {
|
|
|
1635
1784
|
BigInt(yield* FiberMap.size(singletonFibers)),
|
|
1636
1785
|
Context.empty()
|
|
1637
1786
|
)
|
|
1787
|
+
return stops
|
|
1638
1788
|
}))
|
|
1639
1789
|
|
|
1790
|
+
const syncSingletons: Effect.Effect<void> = Effect.flatMap(
|
|
1791
|
+
startSyncSingletons,
|
|
1792
|
+
// wait for the stopped singletons without the lock
|
|
1793
|
+
(stops) => Effect.all(stops, { discard: true })
|
|
1794
|
+
)
|
|
1795
|
+
|
|
1640
1796
|
// --- Entities ---
|
|
1641
1797
|
|
|
1642
1798
|
const reaper = yield* EntityReaper
|
|
@@ -1777,12 +1933,14 @@ const make = Effect.gen(function*() {
|
|
|
1777
1933
|
|
|
1778
1934
|
if (isShutdown.current) return
|
|
1779
1935
|
MutableRef.set(isShutdown, true)
|
|
1936
|
+
yield* notifyShardLockWaiters
|
|
1780
1937
|
if (selfRunner) {
|
|
1781
|
-
yield* Effect.ignore(runnerStorage.unregister(selfRunner.address))
|
|
1938
|
+
yield* Effect.ignore(withRegistrationLock(runnerStorage.unregister(selfRunner.address)))
|
|
1782
1939
|
}
|
|
1783
1940
|
})
|
|
1784
1941
|
|
|
1785
|
-
|
|
1942
|
+
// hand the shards off before the loops stop
|
|
1943
|
+
yield* Scope.addFinalizerExit(shardingScope, (exit) => Effect.andThen(shutdown(exit), handoff))
|
|
1786
1944
|
|
|
1787
1945
|
const activeEntityCount = Effect.gen(function*() {
|
|
1788
1946
|
let count = 0
|