effect 4.0.2 → 4.0.4
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/AGENTS.md +24 -0
- package/CLAUDE.md +24 -0
- package/ai-docs/package.json +2 -2
- package/ai-docs/src/52_graphql/10_basics.ts +111 -0
- package/ai-docs/src/52_graphql/20_testing.ts +45 -0
- package/ai-docs/src/52_graphql/30_batching.ts +116 -0
- package/ai-docs/src/52_graphql/40_aliased-batching.ts +122 -0
- package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
- package/ai-docs/src/52_graphql/index.md +6 -0
- package/dist/Context.d.ts.map +1 -1
- package/dist/Context.js +12 -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/RequestResolver.d.ts.map +1 -1
- package/dist/RequestResolver.js +4 -10
- package/dist/RequestResolver.js.map +1 -1
- package/dist/Scheduler.d.ts +9 -0
- package/dist/Scheduler.d.ts.map +1 -1
- package/dist/Scheduler.js +9 -0
- package/dist/Scheduler.js.map +1 -1
- package/dist/Schema.d.ts +481 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +362 -4
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaGetter.d.ts +4 -0
- package/dist/SchemaGetter.d.ts.map +1 -1
- package/dist/SchemaGetter.js +52 -33
- package/dist/SchemaGetter.js.map +1 -1
- package/dist/Sink.d.ts.map +1 -1
- package/dist/Sink.js +38 -17
- package/dist/Sink.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 +22 -11
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +21 -6
- package/dist/Stream.js.map +1 -1
- package/dist/Utils.js +8 -3
- package/dist/Utils.js.map +1 -1
- package/dist/ai/AnthropicStructuredOutput.js +0 -2
- package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
- package/dist/ai/Chat.d.ts +2 -2
- package/dist/ai/Chat.d.ts.map +1 -1
- package/dist/ai/LanguageModel.d.ts +38 -7
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +25 -7
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpServer.d.ts +61 -0
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +196 -44
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +28 -5
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Response.d.ts +4 -3
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.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 +56 -26
- 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 +187 -66
- 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/encoding/Sse.d.ts +6 -0
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +62 -70
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/graphql/GraphQL.d.ts +349 -0
- package/dist/graphql/GraphQL.d.ts.map +1 -0
- package/dist/graphql/GraphQL.js +207 -0
- package/dist/graphql/GraphQL.js.map +1 -0
- package/dist/graphql/GraphQLClient.d.ts +562 -0
- package/dist/graphql/GraphQLClient.d.ts.map +1 -0
- package/dist/graphql/GraphQLClient.js +498 -0
- package/dist/graphql/GraphQLClient.js.map +1 -0
- package/dist/graphql/GraphQLClientError.d.ts +445 -0
- package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
- package/dist/graphql/GraphQLClientError.js +479 -0
- package/dist/graphql/GraphQLClientError.js.map +1 -0
- package/dist/graphql/GraphQLGroup.d.ts +118 -0
- package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
- package/dist/graphql/GraphQLGroup.js +109 -0
- package/dist/graphql/GraphQLGroup.js.map +1 -0
- package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
- package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
- package/dist/graphql/GraphQLMiddleware.js +130 -0
- package/dist/graphql/GraphQLMiddleware.js.map +1 -0
- package/dist/graphql/GraphQLProtocol.d.ts +731 -0
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
- package/dist/graphql/GraphQLProtocol.js +719 -0
- package/dist/graphql/GraphQLProtocol.js.map +1 -0
- package/dist/graphql/GraphQLResolver.d.ts +453 -0
- package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
- package/dist/graphql/GraphQLResolver.js +552 -0
- package/dist/graphql/GraphQLResolver.js.map +1 -0
- package/dist/graphql/index.d.ts +40 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +41 -0
- package/dist/graphql/index.js.map +1 -0
- 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 +21 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +63 -12
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpEffect.d.ts.map +1 -1
- package/dist/http/HttpEffect.js +14 -7
- package/dist/http/HttpEffect.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +31 -32
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpRouter.d.ts +35 -0
- package/dist/http/HttpRouter.d.ts.map +1 -1
- package/dist/http/HttpRouter.js +35 -0
- package/dist/http/HttpRouter.js.map +1 -1
- package/dist/http/HttpServerResponse.d.ts +8 -6
- package/dist/http/HttpServerResponse.d.ts.map +1 -1
- package/dist/http/HttpServerResponse.js +27 -16
- package/dist/http/HttpServerResponse.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/http/Multipart.d.ts.map +1 -1
- package/dist/http/Multipart.js +15 -1
- package/dist/http/Multipart.js.map +1 -1
- package/dist/http-api/HttpApiScalar.d.ts +5 -0
- package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
- package/dist/http-api/HttpApiScalar.js.map +1 -1
- package/dist/internal/dateTime.js +40 -35
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +19 -9
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/graphql.d.ts +2 -0
- package/dist/internal/graphql.d.ts.map +1 -0
- package/dist/internal/graphql.js +195 -0
- package/dist/internal/graphql.js.map +1 -0
- package/dist/internal/graphqlHeaders.d.ts +4 -0
- package/dist/internal/graphqlHeaders.d.ts.map +1 -0
- package/dist/internal/graphqlHeaders.js +11 -0
- package/dist/internal/graphqlHeaders.js.map +1 -0
- package/dist/internal/request.js +20 -1
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +4 -1
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
- package/dist/internal/schema/toJsonSchemaDocument.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 +369 -0
- package/dist/net/Host.d.ts.map +1 -0
- package/dist/net/Host.js +397 -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 +396 -35
- package/dist/net/NetAddress.d.ts.map +1 -1
- package/dist/net/NetAddress.js +358 -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 +78 -29
- 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 +20 -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 +108 -50
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +24 -2
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +13 -3
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +6 -3
- package/src/Context.ts +12 -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/RequestResolver.ts +4 -10
- package/src/Scheduler.ts +9 -0
- package/src/Schema.ts +677 -5
- package/src/SchemaGetter.ts +47 -35
- package/src/Sink.ts +45 -18
- package/src/Stdio.ts +9 -2
- package/src/Stream.ts +44 -15
- package/src/Utils.ts +8 -5
- package/src/ai/AnthropicStructuredOutput.ts +2 -2
- package/src/ai/Chat.ts +2 -2
- package/src/ai/LanguageModel.ts +96 -24
- package/src/ai/McpServer.ts +288 -94
- package/src/ai/Prompt.ts +24 -10
- package/src/ai/Response.ts +6 -3
- 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 +64 -36
- package/src/cluster/MessageStorage.ts +45 -14
- package/src/cluster/Runners.ts +8 -2
- package/src/cluster/Sharding.ts +219 -58
- package/src/cluster/SqlMessageStorage.ts +118 -84
- package/src/cluster/SqlRunnerStorage.ts +224 -128
- package/src/cluster/internal/entityManager.ts +151 -55
- package/src/encoding/Sse.ts +69 -79
- package/src/graphql/GraphQL.ts +494 -0
- package/src/graphql/GraphQLClient.ts +912 -0
- package/src/graphql/GraphQLClientError.ts +542 -0
- package/src/graphql/GraphQLGroup.ts +167 -0
- package/src/graphql/GraphQLMiddleware.ts +253 -0
- package/src/graphql/GraphQLProtocol.ts +1286 -0
- package/src/graphql/GraphQLResolver.ts +1010 -0
- package/src/graphql/index.ts +48 -0
- package/src/http/FindMyWay/internal/router.ts +4 -7
- package/src/http/HttpClient.ts +110 -12
- package/src/http/HttpEffect.ts +11 -5
- package/src/http/HttpMiddleware.ts +33 -34
- package/src/http/HttpRouter.ts +35 -0
- package/src/http/HttpServerResponse.ts +28 -16
- package/src/http/HttpTraceContext.ts +8 -2
- package/src/http/Multipart.ts +17 -1
- package/src/http-api/HttpApiScalar.ts +5 -0
- package/src/internal/dateTime.ts +13 -10
- package/src/internal/effect.ts +22 -15
- package/src/internal/graphql.ts +263 -0
- package/src/internal/graphqlHeaders.ts +11 -0
- package/src/internal/request.ts +31 -3
- package/src/internal/schema/toCodeDocument.ts +7 -1
- package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
- 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 +544 -0
- package/src/net/IpInterface.ts +117 -7
- package/src/net/IpNetwork.ts +116 -17
- package/src/net/NetAddress.ts +620 -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 +115 -40
- package/src/socket/DatagramSocket.ts +919 -0
- package/src/socket/Socket.ts +28 -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 +160 -47
- package/src/workflow/WorkflowEngine.ts +50 -5
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 ---
|
|
@@ -614,6 +692,12 @@ const make = Effect.gen(function*() {
|
|
|
614
692
|
const storageReadLatch = yield* Latch.make(true)
|
|
615
693
|
const openStorageReadLatch = constant(Effect.asVoid(storageReadLatch.open))
|
|
616
694
|
|
|
695
|
+
type PendingNotification = {
|
|
696
|
+
resume: (_: Effect.Effect<void, EntityNotAssignedToRunner>) => void
|
|
697
|
+
readonly message: Message.IncomingRequest<any>
|
|
698
|
+
}
|
|
699
|
+
const pendingNotifications = new Map<Snowflake.Snowflake, PendingNotification>()
|
|
700
|
+
|
|
617
701
|
const storageReadLock = Semaphore.makeUnsafe(1)
|
|
618
702
|
const withStorageReadLock = storageReadLock.withPermits(1)
|
|
619
703
|
|
|
@@ -647,7 +731,7 @@ const make = Effect.gen(function*() {
|
|
|
647
731
|
if (storageEnabled && initialRunnerAddress) {
|
|
648
732
|
const selfAddress = initialRunnerAddress
|
|
649
733
|
|
|
650
|
-
|
|
734
|
+
const storageReadLoop = Effect.gen(function*() {
|
|
651
735
|
yield* Effect.logDebug("Starting")
|
|
652
736
|
yield* Effect.addFinalizer(() => Effect.logDebug("Shutting down"))
|
|
653
737
|
|
|
@@ -904,8 +988,7 @@ const make = Effect.gen(function*() {
|
|
|
904
988
|
module: "effect/cluster/Sharding",
|
|
905
989
|
fiber: "Storage read loop",
|
|
906
990
|
runner: selfAddress
|
|
907
|
-
})
|
|
908
|
-
Effect.forkIn(shardingScope)
|
|
991
|
+
})
|
|
909
992
|
)
|
|
910
993
|
|
|
911
994
|
// open the storage latch every poll interval
|
|
@@ -1032,20 +1115,28 @@ const make = Effect.gen(function*() {
|
|
|
1032
1115
|
),
|
|
1033
1116
|
Effect.forkIn(shardingScope)
|
|
1034
1117
|
)
|
|
1118
|
+
|
|
1119
|
+
// fork after the state the loop reads has been initialized
|
|
1120
|
+
yield* Effect.forkIn(storageReadLoop, shardingScope)
|
|
1035
1121
|
}
|
|
1036
1122
|
|
|
1037
1123
|
// --- Sending messages ---
|
|
1038
1124
|
|
|
1039
|
-
const sendLocal = <M extends Message.Outgoing<any> | Message.Incoming<any>>(message: M) =>
|
|
1040
|
-
|
|
1125
|
+
const sendLocal = <M extends Message.Outgoing<any> | Message.Incoming<any>>(message: M) => {
|
|
1126
|
+
const address = message.envelope.address
|
|
1127
|
+
const isRequest = message._tag === "IncomingRequest" || message._tag === "OutgoingRequest"
|
|
1128
|
+
const send = Effect.suspend(function loop(): Effect.Effect<
|
|
1041
1129
|
void,
|
|
1042
1130
|
| EntityNotAssignedToRunner
|
|
1043
1131
|
| MailboxFull
|
|
1044
1132
|
| AlreadyProcessingMessage
|
|
1045
1133
|
| (M extends Message.Incoming<any> ? never : PersistenceError)
|
|
1046
1134
|
> {
|
|
1047
|
-
|
|
1048
|
-
|
|
1135
|
+
if (
|
|
1136
|
+
!isEntityOnLocalShards(address) &&
|
|
1137
|
+
// Entities draining from a released shard still accept interrupts and acks
|
|
1138
|
+
(isRequest || !MutableHashSet.has(releasingShards, address.shardId))
|
|
1139
|
+
) {
|
|
1049
1140
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
|
1050
1141
|
}
|
|
1051
1142
|
const state = entityManagers.get(address.entityType)
|
|
@@ -1064,12 +1155,9 @@ const make = Effect.gen(function*() {
|
|
|
1064
1155
|
simulateRemoteSerialization: config.simulateRemoteSerialization
|
|
1065
1156
|
}) as any
|
|
1066
1157
|
})
|
|
1067
|
-
|
|
1068
|
-
type PendingNotification = {
|
|
1069
|
-
resume: (_: Effect.Effect<void, EntityNotAssignedToRunner>) => void
|
|
1070
|
-
readonly message: Message.IncomingRequest<any>
|
|
1158
|
+
return isRequest ? Effect.andThen(awaitShardLock(address.shardId), send) : send
|
|
1071
1159
|
}
|
|
1072
|
-
|
|
1160
|
+
|
|
1073
1161
|
const notifyLocal = <M extends Message.Outgoing<any> | Message.Incoming<any>>(
|
|
1074
1162
|
message: M,
|
|
1075
1163
|
discard: boolean,
|
|
@@ -1087,7 +1175,7 @@ const make = Effect.gen(function*() {
|
|
|
1087
1175
|
const state = entityManagers.get(address.entityType)
|
|
1088
1176
|
if (!state) {
|
|
1089
1177
|
return Effect.flatMap(waitForEntityManager(address.entityType), loop)
|
|
1090
|
-
} else if (state.status === "closed"
|
|
1178
|
+
} else if (state.status === "closed") {
|
|
1091
1179
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
|
1092
1180
|
}
|
|
1093
1181
|
|
|
@@ -1129,7 +1217,10 @@ const make = Effect.gen(function*() {
|
|
|
1129
1217
|
function sendOutgoing(
|
|
1130
1218
|
message: Message.Outgoing<any>,
|
|
1131
1219
|
discard: boolean,
|
|
1132
|
-
retries?: number
|
|
1220
|
+
retries?: number,
|
|
1221
|
+
// The runner serving the request, for its interrupts and acks. Later
|
|
1222
|
+
// attempts fall back to the current shard assignment.
|
|
1223
|
+
runner?: RunnerAddress
|
|
1133
1224
|
): Effect.Effect<
|
|
1134
1225
|
void,
|
|
1135
1226
|
MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
|
|
@@ -1172,7 +1263,9 @@ const make = Effect.gen(function*() {
|
|
|
1172
1263
|
if (shouldFail && MutableRef.get(isShutdown)) {
|
|
1173
1264
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
|
1174
1265
|
}
|
|
1175
|
-
const maybeRunner =
|
|
1266
|
+
const maybeRunner = runner !== undefined
|
|
1267
|
+
? Option.some(runner)
|
|
1268
|
+
: MutableHashMap.get(shardAssignments, address.shardId)
|
|
1176
1269
|
const runnerIsLocal = Option.isSome(maybeRunner) && isLocalRunner(maybeRunner.value)
|
|
1177
1270
|
if (isPersisted) {
|
|
1178
1271
|
return runnerIsLocal
|
|
@@ -1180,6 +1273,9 @@ const make = Effect.gen(function*() {
|
|
|
1180
1273
|
: runnersService.notify({ address: maybeRunner, message, discard })
|
|
1181
1274
|
} else if (Option.isNone(maybeRunner)) {
|
|
1182
1275
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
|
1276
|
+
} else if (message._tag === "OutgoingRequest") {
|
|
1277
|
+
const entry = clientRequests.get(message.envelope.requestId)
|
|
1278
|
+
if (entry) entry.runner = maybeRunner.value
|
|
1183
1279
|
}
|
|
1184
1280
|
return runnerIsLocal
|
|
1185
1281
|
? sendLocal(message)
|
|
@@ -1250,9 +1346,12 @@ const make = Effect.gen(function*() {
|
|
|
1250
1346
|
while (true) {
|
|
1251
1347
|
// Ensure the current runner is registered
|
|
1252
1348
|
if (selfRunner && !isShutdown.current && !MutableHashMap.has(allRunners, selfRunner)) {
|
|
1253
|
-
yield* Effect.
|
|
1254
|
-
|
|
1255
|
-
|
|
1349
|
+
yield* withRegistrationLock(Effect.gen(function*() {
|
|
1350
|
+
if (isShutdown.current) return
|
|
1351
|
+
yield* Effect.logDebug("Registering runner", selfRunner)
|
|
1352
|
+
const machineId = yield* withTimeout(runnerStorage.register(selfRunner, true))
|
|
1353
|
+
yield* snowflakeGen.setMachineId(machineId)
|
|
1354
|
+
}))
|
|
1256
1355
|
}
|
|
1257
1356
|
|
|
1258
1357
|
const runners = yield* withTimeout(runnerStorage.getRunners)
|
|
@@ -1324,6 +1423,7 @@ const make = Effect.gen(function*() {
|
|
|
1324
1423
|
})
|
|
1325
1424
|
yield* Effect.logDebug("New shard assignments", selfShards)
|
|
1326
1425
|
activeShardsLatch.openUnsafe()
|
|
1426
|
+
yield* notifyShardLockWaiters
|
|
1327
1427
|
|
|
1328
1428
|
// update metrics
|
|
1329
1429
|
if (selfRunner) {
|
|
@@ -1361,6 +1461,8 @@ const make = Effect.gen(function*() {
|
|
|
1361
1461
|
readonly context: Context.Context<never>
|
|
1362
1462
|
readonly message: Message.OutgoingRequest<any>
|
|
1363
1463
|
lastChunkId?: Snowflake.Snowflake
|
|
1464
|
+
// the runner a volatile request was last sent to
|
|
1465
|
+
runner?: RunnerAddress
|
|
1364
1466
|
}
|
|
1365
1467
|
const clientRequests = new Map<Snowflake.Snowflake, ClientRequestEntry>()
|
|
1366
1468
|
|
|
@@ -1445,7 +1547,9 @@ const make = Effect.gen(function*() {
|
|
|
1445
1547
|
}),
|
|
1446
1548
|
rpc: entry.rpc
|
|
1447
1549
|
}),
|
|
1448
|
-
false
|
|
1550
|
+
false,
|
|
1551
|
+
undefined,
|
|
1552
|
+
entry.runner
|
|
1449
1553
|
)
|
|
1450
1554
|
}
|
|
1451
1555
|
case "Interrupt": {
|
|
@@ -1476,7 +1580,8 @@ const make = Effect.gen(function*() {
|
|
|
1476
1580
|
rpc: entry.rpc
|
|
1477
1581
|
}),
|
|
1478
1582
|
false,
|
|
1479
|
-
3
|
|
1583
|
+
3,
|
|
1584
|
+
entry.runner
|
|
1480
1585
|
))
|
|
1481
1586
|
}
|
|
1482
1587
|
}
|
|
@@ -1569,6 +1674,47 @@ const make = Effect.gen(function*() {
|
|
|
1569
1674
|
const singletonFibers = yield* FiberMap.make<SingletonAddress>()
|
|
1570
1675
|
const withSingletonLock = Semaphore.makeUnsafe(1).withPermits(1)
|
|
1571
1676
|
|
|
1677
|
+
// Singletons that are stopping. They count as running, so they are not
|
|
1678
|
+
// restarted, and shard releases wait for them. Their teardown is awaited
|
|
1679
|
+
// without the singleton lock, as it can register or stop other singletons.
|
|
1680
|
+
const stoppingSingletons = MutableHashMap.empty<SingletonAddress, Fiber.Fiber<void>>()
|
|
1681
|
+
const isSingletonRegistered = (address: SingletonAddress) => {
|
|
1682
|
+
const map = singletons.get(address.shardId)
|
|
1683
|
+
return map !== undefined && MutableHashMap.has(map, address)
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Called with the singleton lock held. Starts stopping the singleton and
|
|
1687
|
+
// returns the wait for its teardown, to run without the lock.
|
|
1688
|
+
const stopSingletonUnsafe = (address: SingletonAddress, activeTeardown: boolean): Effect.Effect<void> => {
|
|
1689
|
+
const stopping = MutableHashMap.get(stoppingSingletons, address)
|
|
1690
|
+
if (Option.isSome(stopping)) return Fiber.join(stopping.value)
|
|
1691
|
+
const fiber = FiberMap.getUnsafe(singletonFibers, address)
|
|
1692
|
+
if (Option.isNone(fiber)) return Effect.void
|
|
1693
|
+
const interrupt = Fiber.interrupt(fiber.value)
|
|
1694
|
+
const stop = Effect.runForkWith(services)(
|
|
1695
|
+
activeTeardown ? ActiveTeardown.aroundShard(address.shardId, interrupt) : interrupt
|
|
1696
|
+
)
|
|
1697
|
+
MutableHashMap.set(stoppingSingletons, address, stop)
|
|
1698
|
+
stop.addObserver(() => {
|
|
1699
|
+
MutableHashMap.remove(stoppingSingletons, address)
|
|
1700
|
+
// a registration made while the previous singleton was stopping
|
|
1701
|
+
if (isSingletonRegistered(address)) runFork(syncSingletons)
|
|
1702
|
+
})
|
|
1703
|
+
return Fiber.join(stop)
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// Waits for the singletons of the given shards that are still stopping
|
|
1707
|
+
const awaitStoppingSingletons = (shardIds: ReadonlyArray<ShardId>) =>
|
|
1708
|
+
Effect.suspend(() => {
|
|
1709
|
+
const stops = Arr.empty<Fiber.Fiber<void>>()
|
|
1710
|
+
MutableHashMap.forEach(stoppingSingletons, (stop, address) => {
|
|
1711
|
+
if (shardIds.some((shardId) => Equal.equals(shardId, address.shardId))) {
|
|
1712
|
+
stops.push(stop)
|
|
1713
|
+
}
|
|
1714
|
+
})
|
|
1715
|
+
return Fiber.joinAll(stops)
|
|
1716
|
+
})
|
|
1717
|
+
|
|
1572
1718
|
const registerSingleton: Sharding["Service"]["registerSingleton"] = Effect.fnUntraced(
|
|
1573
1719
|
function*(name, run, options) {
|
|
1574
1720
|
const shardGroup = options?.shardGroup ?? "default"
|
|
@@ -1599,32 +1745,38 @@ const make = Effect.gen(function*() {
|
|
|
1599
1745
|
|
|
1600
1746
|
yield* PubSub.publish(events, SingletonRegistered({ address }))
|
|
1601
1747
|
|
|
1602
|
-
// start if we are on the right shard
|
|
1603
|
-
|
|
1748
|
+
// start if we are on the right shard, unless a previous registration of
|
|
1749
|
+
// this singleton is still stopping
|
|
1750
|
+
if (
|
|
1751
|
+
MutableHashSet.has(acquiredShards, address.shardId) &&
|
|
1752
|
+
!MutableHashMap.has(stoppingSingletons, address)
|
|
1753
|
+
) {
|
|
1604
1754
|
yield* Effect.logDebug("Starting singleton", address)
|
|
1605
1755
|
yield* FiberMap.run(singletonFibers, address, wrappedRun)
|
|
1606
1756
|
}
|
|
1607
1757
|
|
|
1608
|
-
yield* Effect.addFinalizer(() =>
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1758
|
+
yield* Effect.addFinalizer(() =>
|
|
1759
|
+
withSingletonLock(Effect.sync(() => {
|
|
1760
|
+
MutableHashMap.remove(singletons.get(address.shardId)!, address)
|
|
1761
|
+
return stopSingletonUnsafe(address, false)
|
|
1762
|
+
})).pipe(Effect.flatten)
|
|
1763
|
+
)
|
|
1613
1764
|
},
|
|
1614
1765
|
withSingletonLock
|
|
1615
1766
|
)
|
|
1616
1767
|
|
|
1617
|
-
|
|
1768
|
+
// Starts and stops singletons to match the acquired shards, returning the
|
|
1769
|
+
// waits for the stopped singletons
|
|
1770
|
+
const startSyncSingletons = withSingletonLock(Effect.gen(function*() {
|
|
1771
|
+
const stops = Arr.empty<Effect.Effect<void>>()
|
|
1618
1772
|
for (const [shardId, map] of singletons) {
|
|
1619
1773
|
for (const [address, run] of map) {
|
|
1620
|
-
const
|
|
1774
|
+
const stopping = MutableHashMap.has(stoppingSingletons, address)
|
|
1775
|
+
const running = stopping || FiberMap.hasUnsafe(singletonFibers, address)
|
|
1621
1776
|
const shouldBeRunning = MutableHashSet.has(acquiredShards, shardId)
|
|
1622
1777
|
if (running && !shouldBeRunning) {
|
|
1623
|
-
yield* Effect.logDebug("Stopping singleton", address)
|
|
1624
|
-
|
|
1625
|
-
address.shardId,
|
|
1626
|
-
FiberMap.remove(singletonFibers, address)
|
|
1627
|
-
)
|
|
1778
|
+
if (!stopping) yield* Effect.logDebug("Stopping singleton", address)
|
|
1779
|
+
stops.push(stopSingletonUnsafe(address, true))
|
|
1628
1780
|
} else if (!running && shouldBeRunning) {
|
|
1629
1781
|
yield* Effect.logDebug("Starting singleton", address)
|
|
1630
1782
|
yield* FiberMap.run(singletonFibers, address, run)
|
|
@@ -1635,8 +1787,15 @@ const make = Effect.gen(function*() {
|
|
|
1635
1787
|
BigInt(yield* FiberMap.size(singletonFibers)),
|
|
1636
1788
|
Context.empty()
|
|
1637
1789
|
)
|
|
1790
|
+
return stops
|
|
1638
1791
|
}))
|
|
1639
1792
|
|
|
1793
|
+
const syncSingletons: Effect.Effect<void> = Effect.flatMap(
|
|
1794
|
+
startSyncSingletons,
|
|
1795
|
+
// wait for the stopped singletons without the lock
|
|
1796
|
+
(stops) => Effect.all(stops, { discard: true })
|
|
1797
|
+
)
|
|
1798
|
+
|
|
1640
1799
|
// --- Entities ---
|
|
1641
1800
|
|
|
1642
1801
|
const reaper = yield* EntityReaper
|
|
@@ -1777,12 +1936,14 @@ const make = Effect.gen(function*() {
|
|
|
1777
1936
|
|
|
1778
1937
|
if (isShutdown.current) return
|
|
1779
1938
|
MutableRef.set(isShutdown, true)
|
|
1939
|
+
yield* notifyShardLockWaiters
|
|
1780
1940
|
if (selfRunner) {
|
|
1781
|
-
yield* Effect.ignore(runnerStorage.unregister(selfRunner.address))
|
|
1941
|
+
yield* Effect.ignore(withRegistrationLock(runnerStorage.unregister(selfRunner.address)))
|
|
1782
1942
|
}
|
|
1783
1943
|
})
|
|
1784
1944
|
|
|
1785
|
-
|
|
1945
|
+
// hand the shards off before the loops stop
|
|
1946
|
+
yield* Scope.addFinalizerExit(shardingScope, (exit) => Effect.andThen(shutdown(exit), handoff))
|
|
1786
1947
|
|
|
1787
1948
|
const activeEntityCount = Effect.gen(function*() {
|
|
1788
1949
|
let count = 0
|