effect 4.0.0-rc.118 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/dist/Array.d.ts +25 -25
- package/dist/Array.d.ts.map +1 -1
- package/dist/Array.js +16 -16
- package/dist/Array.js.map +1 -1
- package/dist/Cache.d.ts.map +1 -1
- package/dist/Cache.js +20 -16
- package/dist/Cache.js.map +1 -1
- package/dist/Cause.d.ts.map +1 -1
- package/dist/Cause.js +1 -1
- package/dist/Cause.js.map +1 -1
- package/dist/Chunk.d.ts +26 -26
- package/dist/Chunk.d.ts.map +1 -1
- package/dist/Chunk.js +15 -15
- package/dist/Chunk.js.map +1 -1
- package/dist/Effect.d.ts +167 -39
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +16 -10
- package/dist/Effect.js.map +1 -1
- package/dist/HashRing.d.ts.map +1 -1
- package/dist/HashRing.js +13 -5
- package/dist/HashRing.js.map +1 -1
- package/dist/MutableHashMap.d.ts.map +1 -1
- package/dist/MutableHashMap.js +4 -22
- package/dist/MutableHashMap.js.map +1 -1
- package/dist/Option.d.ts +17 -17
- package/dist/Option.d.ts.map +1 -1
- package/dist/Option.js +6 -6
- package/dist/Option.js.map +1 -1
- package/dist/Pool.d.ts +9 -5
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +34 -41
- package/dist/Pool.js.map +1 -1
- package/dist/Queue.d.ts +19 -2
- package/dist/Queue.d.ts.map +1 -1
- package/dist/Queue.js +9 -4
- package/dist/Queue.js.map +1 -1
- package/dist/RcMap.d.ts.map +1 -1
- package/dist/RcMap.js +16 -1
- package/dist/RcMap.js.map +1 -1
- package/dist/Record.d.ts +14 -14
- package/dist/Record.d.ts.map +1 -1
- package/dist/Record.js +10 -10
- package/dist/Record.js.map +1 -1
- package/dist/Schema.d.ts +37 -21
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +29 -10
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaAST.d.ts.map +1 -1
- package/dist/SchemaAST.js +116 -97
- package/dist/SchemaAST.js.map +1 -1
- package/dist/SchemaRepresentation.d.ts +3 -1
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +3 -1
- package/dist/SchemaRepresentation.js.map +1 -1
- package/dist/ScopedCache.d.ts.map +1 -1
- package/dist/ScopedCache.js +24 -16
- package/dist/ScopedCache.js.map +1 -1
- package/dist/Stream.d.ts +45 -6
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +55 -44
- package/dist/Stream.js.map +1 -1
- package/dist/ai/LanguageModel.d.ts +8 -0
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +13 -2
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpSchema.d.ts +402 -402
- package/dist/ai/McpSchema.d.ts.map +1 -1
- package/dist/ai/McpSchema.js +1 -1
- package/dist/ai/McpSchema.js.map +1 -1
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +11 -3
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js +22 -12
- package/dist/ai/Response.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
- package/dist/ai/internal/mcpSchema/v2024_11_05.js +3 -1
- package/dist/ai/internal/mcpSchema/v2024_11_05.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +16 -3
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts +32 -8
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +2 -1
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Sharding.d.ts +5 -2
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +15 -8
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +36 -16
- package/dist/cluster/SqlMessageStorage.js.map +1 -1
- package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/cluster/SqlRunnerStorage.js +30 -44
- package/dist/cluster/SqlRunnerStorage.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +63 -62
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/cluster/internal/interruptors.js +8 -0
- package/dist/cluster/internal/interruptors.js.map +1 -1
- package/dist/cluster/internal/resourceMap.js +39 -14
- package/dist/cluster/internal/resourceMap.js.map +1 -1
- package/dist/cluster/internal/resourceRef.js +168 -87
- package/dist/cluster/internal/resourceRef.js.map +1 -1
- package/dist/http/HttpEffect.d.ts +1 -0
- package/dist/http/HttpEffect.d.ts.map +1 -1
- package/dist/http/HttpEffect.js +29 -22
- package/dist/http/HttpEffect.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +3 -2
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpStaticServer.d.ts.map +1 -1
- package/dist/http/HttpStaticServer.js +14 -0
- package/dist/http/HttpStaticServer.js.map +1 -1
- package/dist/http/HttpTraceContext.d.ts +1 -1
- package/dist/http/HttpTraceContext.d.ts.map +1 -1
- package/dist/http/HttpTraceContext.js +4 -4
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/http-api/HttpApiError.d.ts +1 -0
- package/dist/http-api/HttpApiError.d.ts.map +1 -1
- package/dist/http-api/HttpApiError.js +1 -0
- package/dist/http-api/HttpApiError.js.map +1 -1
- package/dist/internal/core.js +43 -2
- package/dist/internal/core.js.map +1 -1
- package/dist/internal/dateTime.js +1 -1
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +138 -165
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/rcRef.js +59 -23
- package/dist/internal/rcRef.js.map +1 -1
- package/dist/internal/schema/annotations.js +1 -7
- package/dist/internal/schema/annotations.js.map +1 -1
- package/dist/internal/schema/codegen.js +56 -28
- package/dist/internal/schema/codegen.js.map +1 -1
- package/dist/internal/schema/fromJsonSchemaDocument.js +11 -8
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +10 -30
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/schema/toCodec.js +1 -15
- package/dist/internal/schema/toCodec.js.map +1 -1
- package/dist/internal/schema/toEquivalence.js +4 -3
- package/dist/internal/schema/toEquivalence.js.map +1 -1
- package/dist/internal/schema/toFormatter.js +4 -4
- package/dist/internal/schema/toFormatter.js.map +1 -1
- package/dist/reactivity/Atom.d.ts.map +1 -1
- package/dist/reactivity/Atom.js +6 -2
- package/dist/reactivity/Atom.js.map +1 -1
- package/dist/reactivity/AtomRpc.d.ts +3 -3
- package/dist/reactivity/AtomRpc.d.ts.map +1 -1
- package/dist/rpc/Rpc.d.ts +2 -2
- package/dist/rpc/Rpc.d.ts.map +1 -1
- package/dist/rpc/RpcClient.d.ts +7 -7
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +21 -16
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcMessage.d.ts +1 -1
- package/dist/rpc/RpcMessage.d.ts.map +1 -1
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +2 -2
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/rpc/Utils.d.ts +2 -3
- package/dist/rpc/Utils.d.ts.map +1 -1
- package/dist/rpc/Utils.js +3 -4
- package/dist/rpc/Utils.js.map +1 -1
- package/dist/schema/SchemaCompiler/runtime.js +6 -1
- package/dist/schema/SchemaCompiler/runtime.js.map +1 -1
- package/dist/testing/TestSchema.d.ts +234 -6
- package/dist/testing/TestSchema.d.ts.map +1 -1
- package/dist/testing/TestSchema.js +244 -25
- package/dist/testing/TestSchema.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/workflow/DurableDeferred.js +1 -1
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/Workflow.js +1 -1
- package/dist/workflow/Workflow.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +2 -0
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +1 -0
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +1 -1
- package/src/Array.ts +31 -31
- package/src/Cache.ts +22 -16
- package/src/Cause.ts +1 -1
- package/src/Chunk.ts +29 -29
- package/src/Effect.ts +170 -39
- package/src/HashRing.ts +14 -5
- package/src/MutableHashMap.ts +4 -24
- package/src/Option.ts +19 -19
- package/src/Pool.ts +28 -36
- package/src/Queue.ts +26 -6
- package/src/RcMap.ts +26 -2
- package/src/Record.ts +20 -20
- package/src/Schema.ts +48 -24
- package/src/SchemaAST.ts +135 -115
- package/src/SchemaRepresentation.ts +3 -1
- package/src/ScopedCache.ts +27 -16
- package/src/Stream.ts +158 -88
- package/src/ai/LanguageModel.ts +27 -2
- package/src/ai/McpSchema.ts +3 -3
- package/src/ai/McpServer.ts +12 -3
- package/src/ai/Response.ts +27 -12
- package/src/ai/internal/mcpProtocol/v2024_11_05.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_03_26.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_06_18.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_11_25.ts +1 -0
- package/src/ai/internal/mcpSchema/v2024_11_05.ts +4 -2
- package/src/cluster/ClusterWorkflowEngine.ts +26 -5
- package/src/cluster/MessageStorage.ts +22 -5
- package/src/cluster/Sharding.ts +32 -28
- package/src/cluster/SqlMessageStorage.ts +45 -21
- package/src/cluster/SqlRunnerStorage.ts +85 -113
- package/src/cluster/internal/entityManager.ts +68 -80
- package/src/cluster/internal/interruptors.ts +13 -0
- package/src/cluster/internal/resourceMap.ts +55 -17
- package/src/cluster/internal/resourceRef.ts +189 -96
- package/src/http/HttpEffect.ts +15 -9
- package/src/http/HttpMiddleware.ts +8 -2
- package/src/http/HttpStaticServer.ts +16 -0
- package/src/http/HttpTraceContext.ts +8 -4
- package/src/http-api/HttpApiError.ts +1 -0
- package/src/internal/core.ts +56 -2
- package/src/internal/dateTime.ts +3 -1
- package/src/internal/effect.ts +146 -173
- package/src/internal/rcRef.ts +83 -33
- package/src/internal/schema/annotations.ts +0 -9
- package/src/internal/schema/codegen.ts +90 -43
- package/src/internal/schema/fromJsonSchemaDocument.ts +5 -3
- package/src/internal/schema/toCodeDocument.ts +10 -47
- package/src/internal/schema/toCodec.ts +3 -16
- package/src/internal/schema/toEquivalence.ts +4 -5
- package/src/internal/schema/toFormatter.ts +4 -6
- package/src/reactivity/Atom.ts +6 -2
- package/src/reactivity/AtomRpc.ts +5 -5
- package/src/rpc/Rpc.ts +2 -2
- package/src/rpc/RpcClient.ts +35 -32
- package/src/rpc/RpcMessage.ts +1 -1
- package/src/rpc/RpcServer.ts +2 -1
- package/src/rpc/Utils.ts +4 -10
- package/src/schema/SchemaCompiler/runtime.ts +17 -1
- package/src/testing/TestSchema.ts +315 -63
- package/src/testing/index.ts +1 -0
- package/src/workflow/DurableDeferred.ts +1 -1
- package/src/workflow/Workflow.ts +1 -1
- package/src/workflow/WorkflowEngine.ts +4 -0
|
@@ -299,7 +299,14 @@ export const make = Effect.gen(function*() {
|
|
|
299
299
|
)
|
|
300
300
|
|
|
301
301
|
if (Option.isNone(maybeSuspended)) return
|
|
302
|
-
|
|
302
|
+
if (
|
|
303
|
+
!(yield* sharding.reset(
|
|
304
|
+
Snowflake.Snowflake(maybeSuspended.value.requestId),
|
|
305
|
+
{ expectedReplyId: Snowflake.Snowflake(maybeSuspended.value.id) }
|
|
306
|
+
))
|
|
307
|
+
) {
|
|
308
|
+
return yield* Effect.fail(new Error("Failed to reset workflow run"))
|
|
309
|
+
}
|
|
303
310
|
yield* sharding.pollStorage
|
|
304
311
|
})
|
|
305
312
|
|
|
@@ -404,7 +411,7 @@ export const make = Effect.gen(function*() {
|
|
|
404
411
|
// Replays reuse the request id but track a fresh set of awaits.
|
|
405
412
|
let currentRun: {
|
|
406
413
|
readonly request: Entity.Request<any>
|
|
407
|
-
readonly
|
|
414
|
+
readonly instance: WorkflowEngine.WorkflowInstance["Service"]
|
|
408
415
|
} | undefined
|
|
409
416
|
// Concurrent wakes share one wait for the current run to publish its reply.
|
|
410
417
|
const resumeGate = Semaphore.makeUnsafe(1)
|
|
@@ -421,7 +428,7 @@ export const make = Effect.gen(function*() {
|
|
|
421
428
|
return {
|
|
422
429
|
run: (request: Entity.Request<any>) => {
|
|
423
430
|
const instance = WorkflowEngine.WorkflowInstance.initial(workflow, executionId)
|
|
424
|
-
currentRun = { request,
|
|
431
|
+
currentRun = { request, instance }
|
|
425
432
|
const parent = (request.payload as any)[payloadParentKey] as
|
|
426
433
|
| { workflowName: string; executionId: string }
|
|
427
434
|
| undefined
|
|
@@ -515,9 +522,13 @@ export const make = Effect.gen(function*() {
|
|
|
515
522
|
// registered yet, a later await will see the result just stored.
|
|
516
523
|
// Check when the wake runs and skip preemption too: interrupting
|
|
517
524
|
// a later await would require waiting for its reply before reset.
|
|
525
|
+
// A run completing its own deferred also reads it from the cache
|
|
526
|
+
// when it awaits later, so that completion never wakes the run.
|
|
518
527
|
const wake = Effect.suspend(() =>
|
|
519
|
-
currentRun && !currentRun.awaitedDeferreds.has(payload.name)
|
|
520
|
-
?
|
|
528
|
+
currentRun && !currentRun.instance.awaitedDeferreds.has(payload.name)
|
|
529
|
+
? currentRun.instance.completedDeferreds.has(payload.name)
|
|
530
|
+
? Effect.void
|
|
531
|
+
: ensureSuccess(resume(workflow, executionId))
|
|
521
532
|
: deferredState.deferredDone(executionId, payload.name).pipe(Effect.andThen(resumeCurrentRun))
|
|
522
533
|
)
|
|
523
534
|
// An asynchronous reply releases the RPC concurrency permit while
|
|
@@ -696,6 +707,16 @@ export const make = Effect.gen(function*() {
|
|
|
696
707
|
|
|
697
708
|
deferredDone: Effect.fnUntraced(
|
|
698
709
|
function*({ deferredName, executionId, exit, workflowName }) {
|
|
710
|
+
const instance = yield* Effect.serviceOption(WorkflowEngine.WorkflowInstance)
|
|
711
|
+
// Activities run with their own instance, so their completions still
|
|
712
|
+
// wake a run that suspended before reaching the await. An interrupt
|
|
713
|
+
// must wake the run even when it signals itself.
|
|
714
|
+
if (
|
|
715
|
+
Option.isSome(instance) && instance.value.executionId === executionId &&
|
|
716
|
+
deferredName !== InterruptSignal.name
|
|
717
|
+
) {
|
|
718
|
+
instance.value.completedDeferreds.add(deferredName)
|
|
719
|
+
}
|
|
699
720
|
const workflow = workflows.get(workflowName)
|
|
700
721
|
if (workflow) {
|
|
701
722
|
return yield* Effect.orDie(sendDiscard({
|
|
@@ -74,9 +74,16 @@ export class MessageStorage extends Context.Service<MessageStorage, {
|
|
|
74
74
|
) => Effect.Effect<void, PersistenceError | MalformedMessage>
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
* Clear
|
|
77
|
+
* Clear replies for the given request id. When options.expectedReplyId is
|
|
78
|
+
* provided, clear only if it is still the latest reply at the storage boundary.
|
|
79
|
+
* Without it, clear unconditionally.
|
|
80
|
+
* Custom storage implementations must honor this condition to protect
|
|
81
|
+
* workflows from stale concurrent resumes.
|
|
78
82
|
*/
|
|
79
|
-
readonly clearReplies: (
|
|
83
|
+
readonly clearReplies: (
|
|
84
|
+
requestId: Snowflake.Snowflake,
|
|
85
|
+
options?: { readonly expectedReplyId?: Snowflake.Snowflake | undefined }
|
|
86
|
+
) => Effect.Effect<void, PersistenceError>
|
|
80
87
|
|
|
81
88
|
/**
|
|
82
89
|
* Retrieves the replies for the specified requests.
|
|
@@ -350,9 +357,16 @@ export type Encoded = {
|
|
|
350
357
|
readonly saveReply: (reply: Reply.Encoded) => Effect.Effect<void, PersistenceError>
|
|
351
358
|
|
|
352
359
|
/**
|
|
353
|
-
* Remove the replies for the specified request.
|
|
360
|
+
* Remove the replies for the specified request. If options.expectedReplyId
|
|
361
|
+
* is provided, compare it with the latest reply atomically before clearing.
|
|
362
|
+
* Without it, clear unconditionally.
|
|
363
|
+
* Custom storage implementations must honor this condition to protect
|
|
364
|
+
* workflows from stale concurrent resumes.
|
|
354
365
|
*/
|
|
355
|
-
readonly clearReplies: (
|
|
366
|
+
readonly clearReplies: (
|
|
367
|
+
requestId: Snowflake.Snowflake,
|
|
368
|
+
options?: { readonly expectedReplyId?: Snowflake.Snowflake | undefined }
|
|
369
|
+
) => Effect.Effect<void, PersistenceError>
|
|
356
370
|
|
|
357
371
|
/**
|
|
358
372
|
* Retrieves the request id for the specified primary key.
|
|
@@ -1054,10 +1068,13 @@ export class MemoryDriver extends Context.Service<MemoryDriver>()("effect/cluste
|
|
|
1054
1068
|
replyIds.add(reply.id)
|
|
1055
1069
|
replyLatch.openUnsafe()
|
|
1056
1070
|
}),
|
|
1057
|
-
clearReplies: (id) =>
|
|
1071
|
+
clearReplies: (id, options) =>
|
|
1058
1072
|
Effect.sync(() => {
|
|
1059
1073
|
const entry = requests.get(String(id))
|
|
1060
1074
|
if (!entry) return
|
|
1075
|
+
if (
|
|
1076
|
+
options?.expectedReplyId !== undefined && entry.replies.at(-1)?.id !== String(options.expectedReplyId)
|
|
1077
|
+
) return
|
|
1061
1078
|
entry.replies = []
|
|
1062
1079
|
entry.lastReceivedChunk = undefined
|
|
1063
1080
|
unprocessed.add(entry.envelope)
|
package/src/cluster/Sharding.ts
CHANGED
|
@@ -195,9 +195,13 @@ export class Sharding extends Context.Service<Sharding, {
|
|
|
195
195
|
>
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
* Reset the state of a message
|
|
198
|
+
* Reset the state of a message. When options.expectedReplyId is provided,
|
|
199
|
+
* reset only if it is still the latest reply. Otherwise reset unconditionally.
|
|
199
200
|
*/
|
|
200
|
-
readonly reset: (
|
|
201
|
+
readonly reset: (
|
|
202
|
+
requestId: Snowflake.Snowflake,
|
|
203
|
+
options?: { readonly expectedReplyId?: Snowflake.Snowflake | undefined }
|
|
204
|
+
) => Effect.Effect<boolean>
|
|
201
205
|
|
|
202
206
|
/**
|
|
203
207
|
* Trigger a storage read, which will read all unprocessed messages.
|
|
@@ -545,30 +549,27 @@ const make = Effect.gen(function*() {
|
|
|
545
549
|
return Effect.logInfo("Shard lock storage has recovered")
|
|
546
550
|
})
|
|
547
551
|
|
|
548
|
-
const refreshShardLocks = Effect.
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (!acquired.includes(shardId)) {
|
|
557
|
-
MutableHashSet.remove(acquiredShards, shardId)
|
|
558
|
-
MutableHashSet.add(releasingShards, shardId)
|
|
559
|
-
}
|
|
552
|
+
const refreshShardLocks = Effect.gen(function*() {
|
|
553
|
+
const refreshed = [...acquiredShards, ...releasingShards]
|
|
554
|
+
const acquired = yield* runnerStorage.refresh(selfAddress, refreshed)
|
|
555
|
+
// Shards acquired during the request are absent from its response.
|
|
556
|
+
for (const shardId of refreshed) {
|
|
557
|
+
if (MutableHashSet.has(acquiredShards, shardId) && !acquired.includes(shardId)) {
|
|
558
|
+
MutableHashSet.remove(acquiredShards, shardId)
|
|
559
|
+
MutableHashSet.add(releasingShards, shardId)
|
|
560
560
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
561
|
+
}
|
|
562
|
+
for (let i = 0; i < acquired.length; i++) {
|
|
563
|
+
const shardId = acquired[i]
|
|
564
|
+
if (!MutableHashSet.has(selfShards, shardId)) {
|
|
565
|
+
MutableHashSet.remove(acquiredShards, shardId)
|
|
566
|
+
MutableHashSet.add(releasingShards, shardId)
|
|
567
567
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
568
|
+
}
|
|
569
|
+
if (MutableHashSet.size(releasingShards) > 0) {
|
|
570
|
+
yield* activeShardsLatch.open
|
|
571
|
+
}
|
|
572
|
+
}).pipe(
|
|
572
573
|
Effect.retry({
|
|
573
574
|
times: 5,
|
|
574
575
|
schedule: Schedule.spaced(50)
|
|
@@ -1208,8 +1209,8 @@ const make = Effect.gen(function*() {
|
|
|
1208
1209
|
)
|
|
1209
1210
|
}
|
|
1210
1211
|
|
|
1211
|
-
const reset: Sharding["Service"]["reset"] = (requestId) =>
|
|
1212
|
-
Effect.matchCause(storage.clearReplies(requestId), {
|
|
1212
|
+
const reset: Sharding["Service"]["reset"] = (requestId, options) =>
|
|
1213
|
+
Effect.matchCause(storage.clearReplies(requestId, options), {
|
|
1213
1214
|
onSuccess: () => true,
|
|
1214
1215
|
onFailure: () => false
|
|
1215
1216
|
})
|
|
@@ -1458,8 +1459,10 @@ const make = Effect.gen(function*() {
|
|
|
1458
1459
|
// for durable messages, we ignore interrupts on shutdown or as a
|
|
1459
1460
|
// result of a shard being resassigned
|
|
1460
1461
|
const caller = Context.getOption(entry.context, CurrentAddress).valueOrUndefined
|
|
1462
|
+
const singletonShard = Context.getOption(entry.context, SingletonShardTag).valueOrUndefined
|
|
1461
1463
|
const isTransientInterrupt = MutableRef.get(isShutdown) ||
|
|
1462
|
-
(caller !== undefined && ActiveTeardown.isActive(caller))
|
|
1464
|
+
(caller !== undefined && ActiveTeardown.isActive(caller)) ||
|
|
1465
|
+
(singletonShard !== undefined && ActiveTeardown.isShardActive(singletonShard))
|
|
1463
1466
|
if (isTransientInterrupt && Context.get(entry.message.annotations, Persisted)) {
|
|
1464
1467
|
return Effect.void
|
|
1465
1468
|
}
|
|
@@ -1588,7 +1591,7 @@ const make = Effect.gen(function*() {
|
|
|
1588
1591
|
Effect.andThen(Effect.never),
|
|
1589
1592
|
Effect.scoped,
|
|
1590
1593
|
Effect.provideService(CurrentLogAnnotations, {}),
|
|
1591
|
-
Effect.provideContext(services),
|
|
1594
|
+
Effect.provideContext(Context.add(services, SingletonShardTag, address.shardId)),
|
|
1592
1595
|
Effect.orDie,
|
|
1593
1596
|
Effect.interruptible
|
|
1594
1597
|
) as Effect.Effect<never>
|
|
@@ -1851,3 +1854,4 @@ export const layer: Layer.Layer<
|
|
|
1851
1854
|
// Utilities
|
|
1852
1855
|
|
|
1853
1856
|
const ClientAddressTag = Context.Service<EntityAddress>("effect/cluster/Sharding/ClientAddress")
|
|
1857
|
+
const SingletonShardTag = Context.Service<ShardId>("effect/cluster/Sharding/SingletonShard")
|
|
@@ -29,6 +29,7 @@ import * as Migrator from "../sql/Migrator.ts"
|
|
|
29
29
|
import * as SqlClient from "../sql/SqlClient.ts"
|
|
30
30
|
import type { Row } from "../sql/SqlConnection.ts"
|
|
31
31
|
import { isSqlError, type SqlError } from "../sql/SqlError.ts"
|
|
32
|
+
import type { Fragment } from "../sql/Statement.ts"
|
|
32
33
|
import { PersistenceError } from "./ClusterError.ts"
|
|
33
34
|
import type * as EntityAddress from "./EntityAddress.ts"
|
|
34
35
|
import type * as Envelope from "./Envelope.ts"
|
|
@@ -494,6 +495,28 @@ export const makeEncoded: (options?: {
|
|
|
494
495
|
? Effect.succeed([])
|
|
495
496
|
: getUnprocessedMessagesForDialect(shardIds, now, unprocessedFilters(options))
|
|
496
497
|
|
|
498
|
+
const resetUnprocessed = sql.onDialectOrElse({
|
|
499
|
+
// Match the claim query's lock order to avoid deadlocks during resets.
|
|
500
|
+
pg: () => (filter: Fragment) =>
|
|
501
|
+
sql`
|
|
502
|
+
WITH messages AS (
|
|
503
|
+
SELECT id FROM ${messagesTableSql}
|
|
504
|
+
WHERE processed = ${sqlFalse} AND ${filter}
|
|
505
|
+
ORDER BY rowid ASC
|
|
506
|
+
FOR UPDATE
|
|
507
|
+
)
|
|
508
|
+
UPDATE ${messagesTableSql}
|
|
509
|
+
SET last_read = NULL
|
|
510
|
+
WHERE id IN (SELECT id FROM messages)
|
|
511
|
+
`,
|
|
512
|
+
orElse: () => (filter: Fragment) =>
|
|
513
|
+
sql`
|
|
514
|
+
UPDATE ${messagesTableSql}
|
|
515
|
+
SET last_read = NULL
|
|
516
|
+
WHERE processed = ${sqlFalse} AND ${filter}
|
|
517
|
+
`
|
|
518
|
+
})
|
|
519
|
+
|
|
497
520
|
const encoded: MessageStorage.Encoded = {
|
|
498
521
|
saveEnvelope: ({ deliverAt, envelope, primaryKey }) =>
|
|
499
522
|
Effect.suspend(() => {
|
|
@@ -576,14 +599,25 @@ export const makeEncoded: (options?: {
|
|
|
576
599
|
),
|
|
577
600
|
|
|
578
601
|
clearReplies: Effect.fnUntraced(
|
|
579
|
-
function*(requestId) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
String(requestId)
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
602
|
+
function*(requestId, options) {
|
|
603
|
+
const expectedReplyId = options?.expectedReplyId
|
|
604
|
+
if (expectedReplyId === undefined) {
|
|
605
|
+
yield* sql`DELETE FROM ${repliesTableSql} WHERE request_id = ${String(requestId)} AND kind = 0`
|
|
606
|
+
yield* sql`DELETE FROM ${messagesTableSql} WHERE request_id = ${
|
|
607
|
+
String(requestId)
|
|
608
|
+
} AND kind = ${messageKindInterrupt}`
|
|
609
|
+
yield* sql`UPDATE ${messagesTableSql} SET processed = ${sqlFalse}, last_reply_id = NULL, last_read = NULL WHERE request_id = ${
|
|
610
|
+
String(requestId)
|
|
611
|
+
}`
|
|
612
|
+
return
|
|
613
|
+
}
|
|
614
|
+
const id = String(requestId)
|
|
615
|
+
const expected = String(expectedReplyId)
|
|
616
|
+
// Check at the storage boundary, not at the earlier workflow read.
|
|
617
|
+
yield* sql`DELETE FROM ${repliesTableSql} WHERE request_id = ${id} AND kind = 0
|
|
618
|
+
AND EXISTS (SELECT 1 FROM ${messagesTableSql} WHERE id = ${id} AND last_reply_id = ${expected})`
|
|
619
|
+
yield* sql`UPDATE ${messagesTableSql} SET processed = ${sqlFalse}, last_reply_id = NULL, last_read = NULL
|
|
620
|
+
WHERE request_id = ${id} AND last_reply_id = ${expected}`
|
|
587
621
|
},
|
|
588
622
|
sql.withTransaction,
|
|
589
623
|
PersistenceError.refail,
|
|
@@ -705,11 +739,7 @@ export const makeEncoded: (options?: {
|
|
|
705
739
|
resetAddresses: (addresses) =>
|
|
706
740
|
addresses.length === 0
|
|
707
741
|
? Effect.void
|
|
708
|
-
: sql`
|
|
709
|
-
UPDATE ${messagesTableSql}
|
|
710
|
-
SET last_read = NULL
|
|
711
|
-
WHERE processed = ${sqlFalse}
|
|
712
|
-
AND (${
|
|
742
|
+
: resetUnprocessed(sql`(${
|
|
713
743
|
sql.or(
|
|
714
744
|
groupAddresses(addresses).map(
|
|
715
745
|
(group) =>
|
|
@@ -720,8 +750,7 @@ export const makeEncoded: (options?: {
|
|
|
720
750
|
])
|
|
721
751
|
)
|
|
722
752
|
)
|
|
723
|
-
})
|
|
724
|
-
`.pipe(
|
|
753
|
+
})`).pipe(
|
|
725
754
|
Effect.asVoid,
|
|
726
755
|
PersistenceError.refail,
|
|
727
756
|
withTracerDisabled
|
|
@@ -750,12 +779,7 @@ export const makeEncoded: (options?: {
|
|
|
750
779
|
),
|
|
751
780
|
|
|
752
781
|
resetShards: (shardIds) =>
|
|
753
|
-
sql`
|
|
754
|
-
UPDATE ${messagesTableSql}
|
|
755
|
-
SET last_read = NULL
|
|
756
|
-
WHERE processed = ${sqlFalse}
|
|
757
|
-
AND shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})
|
|
758
|
-
`.pipe(
|
|
782
|
+
resetUnprocessed(sql`shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})`).pipe(
|
|
759
783
|
Effect.asVoid,
|
|
760
784
|
PersistenceError.refail,
|
|
761
785
|
withTracerDisabled
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
* @since 4.0.0
|
|
12
12
|
*/
|
|
13
13
|
import * as Arr from "../Array.ts"
|
|
14
|
+
import * as Cause from "../Cause.ts"
|
|
14
15
|
import * as Duration from "../Duration.ts"
|
|
15
16
|
import * as Effect from "../Effect.ts"
|
|
16
|
-
import * as Exit from "../Exit.ts"
|
|
17
17
|
import * as Fiber from "../Fiber.ts"
|
|
18
18
|
import * as Layer from "../Layer.ts"
|
|
19
19
|
import * as Scope from "../Scope.ts"
|
|
20
20
|
import * as SqlClient from "../sql/SqlClient.ts"
|
|
21
|
+
import type { Connection } from "../sql/SqlConnection.ts"
|
|
21
22
|
import type { SqlError } from "../sql/SqlError.ts"
|
|
22
23
|
import type * as Statement from "../sql/Statement.ts"
|
|
23
24
|
import { PersistenceError } from "./ClusterError.ts"
|
|
@@ -156,47 +157,28 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
156
157
|
)
|
|
157
158
|
})
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const rebuildLockConn = (generation: number) => {
|
|
165
|
-
if (
|
|
166
|
-
!lockConn ||
|
|
167
|
-
lockConnRebuilding ||
|
|
168
|
-
generation !== lockConnGeneration ||
|
|
169
|
-
lockConn.state.current._tag === "Closed"
|
|
170
|
-
) return Effect.void
|
|
171
|
-
lockConnRebuilding = true
|
|
172
|
-
// The rebuild starts by closing the previous scope, releasing the
|
|
173
|
-
// unresponsive connection back to the pool. Bound it with `withDeadline`
|
|
174
|
-
// so a release that never completes cannot leave `lockConnRebuilding` set
|
|
175
|
-
// forever, which would disable every subsequent rebuild.
|
|
176
|
-
return withDeadline(lockConn.rebuildUnsafe()).pipe(
|
|
160
|
+
const rebuildLockConn = (from?: readonly [Connection, number]) => {
|
|
161
|
+
const rebuild = lockConn?.rebuildUnsafe({ from })
|
|
162
|
+
if (!rebuild) return Effect.void
|
|
163
|
+
// Bound release so a stalled connection cannot block subsequent rebuilds.
|
|
164
|
+
return withDeadline(rebuild).pipe(
|
|
177
165
|
Effect.exit,
|
|
178
|
-
Effect.tap((exit) =>
|
|
179
|
-
Effect.sync(() => {
|
|
180
|
-
if (Exit.isSuccess(exit) && lockConn.state.current._tag === "Acquired") {
|
|
181
|
-
lockConnGeneration++
|
|
182
|
-
}
|
|
183
|
-
})
|
|
184
|
-
),
|
|
185
|
-
Effect.ensuring(Effect.sync(() => {
|
|
186
|
-
lockConnRebuilding = false
|
|
187
|
-
})),
|
|
188
166
|
Effect.forkIn(layerScope, { startImmediately: true }),
|
|
189
167
|
Effect.asVoid
|
|
190
168
|
)
|
|
191
169
|
}
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
Effect.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
170
|
+
// SQL failures replace the held connection; deadlines are handled by withLockOperationDeadline.
|
|
171
|
+
const useLockConn = <A, E, R>(
|
|
172
|
+
f: (conn: Connection, pid: number) => Effect.Effect<A, E, R>
|
|
173
|
+
): Effect.Effect<A, E, R> =>
|
|
174
|
+
Effect.flatMap(
|
|
175
|
+
lockConn!.await,
|
|
176
|
+
(held) =>
|
|
177
|
+
Effect.onError(
|
|
178
|
+
f(held[0], held[1]),
|
|
179
|
+
(cause) => Cause.hasInterruptsOnly(cause) ? Effect.void : rebuildLockConn(held)
|
|
180
|
+
)
|
|
181
|
+
)
|
|
200
182
|
|
|
201
183
|
const runnersTable = table("runners")
|
|
202
184
|
const runnersTableSql = sql(runnersTable)
|
|
@@ -378,30 +360,21 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
378
360
|
const execWithLockConn = <A>(effect: Statement.Statement<A>): Effect.Effect<unknown, SqlError> => {
|
|
379
361
|
if (!lockConn) return effect
|
|
380
362
|
const [query, params] = effect.compile()
|
|
381
|
-
return
|
|
382
|
-
Effect.flatMap(([conn]) => conn.executeRaw(query, params)),
|
|
383
|
-
onErrorRebuildLockConn
|
|
384
|
-
)
|
|
363
|
+
return useLockConn((conn) => conn.executeRaw(query, params))
|
|
385
364
|
}
|
|
386
365
|
const execWithLockConnUnprepared = <A>(
|
|
387
366
|
effect: Statement.Statement<A>
|
|
388
367
|
): Effect.Effect<ReadonlyArray<ReadonlyArray<any>>, SqlError> => {
|
|
389
368
|
if (!lockConn) return effect.values
|
|
390
369
|
const [query, params] = effect.compile()
|
|
391
|
-
return
|
|
392
|
-
Effect.flatMap(([conn]) => conn.executeUnprepared(query, params, undefined)),
|
|
393
|
-
onErrorRebuildLockConn
|
|
394
|
-
)
|
|
370
|
+
return useLockConn((conn) => conn.executeUnprepared(query, params, undefined))
|
|
395
371
|
}
|
|
396
372
|
const execWithLockConnValues = <A>(
|
|
397
373
|
effect: Statement.Statement<A>
|
|
398
374
|
): Effect.Effect<ReadonlyArray<ReadonlyArray<any>>, SqlError> => {
|
|
399
375
|
if (!lockConn) return effect.values
|
|
400
376
|
const [query, params] = effect.compile()
|
|
401
|
-
return
|
|
402
|
-
Effect.flatMap(([conn]) => conn.executeValues(query, params)),
|
|
403
|
-
onErrorRebuildLockConn
|
|
404
|
-
)
|
|
377
|
+
return useLockConn((conn) => conn.executeValues(query, params))
|
|
405
378
|
}
|
|
406
379
|
|
|
407
380
|
const acquireLock = sql.onDialectOrElse({
|
|
@@ -425,33 +398,33 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
425
398
|
)
|
|
426
399
|
}
|
|
427
400
|
}
|
|
428
|
-
return
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
acquiredShardIds.push(shardId)
|
|
441
|
-
toAcquire.delete(lockNum)
|
|
442
|
-
}
|
|
443
|
-
if (toAcquire.size === 0) {
|
|
444
|
-
return acquiredShardIds
|
|
445
|
-
}
|
|
446
|
-
const rows = yield* conn.executeUnprepared(`SELECT ${pgLocks(toAcquire)}`, [], undefined)
|
|
447
|
-
const results = rows[0] as Record<string, boolean>
|
|
448
|
-
for (const shardId in results) {
|
|
449
|
-
if (results[shardId]) {
|
|
401
|
+
return (_address: string, shardIds: ReadonlyArray<string>) =>
|
|
402
|
+
useLockConn(Effect.fnUntraced(function*(conn, pid) {
|
|
403
|
+
const acquiredShardIds: Array<string> = []
|
|
404
|
+
const toAcquire = new Map(shardIds.map((shardId) => [lockNumbers.get(shardId)!, shardId]))
|
|
405
|
+
const takenLocks = yield* conn.executeValues(
|
|
406
|
+
`SELECT objid FROM pg_locks WHERE locktype = 'advisory' AND granted = true AND classid = ${pgLockNamespaceOid} AND objsubid = 2 AND pid = ${pid} ORDER BY objid`,
|
|
407
|
+
[]
|
|
408
|
+
)
|
|
409
|
+
for (let i = 0; i < takenLocks.length; i++) {
|
|
410
|
+
const lockNum = takenLocks[i][0] as number
|
|
411
|
+
const shardId = toAcquire.get(lockNum)
|
|
412
|
+
if (shardId === undefined) continue
|
|
450
413
|
acquiredShardIds.push(shardId)
|
|
414
|
+
toAcquire.delete(lockNum)
|
|
451
415
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
416
|
+
if (toAcquire.size === 0) {
|
|
417
|
+
return acquiredShardIds
|
|
418
|
+
}
|
|
419
|
+
const rows = yield* conn.executeUnprepared(`SELECT ${pgLocks(toAcquire)}`, [], undefined)
|
|
420
|
+
const results = rows[0] as Record<string, boolean>
|
|
421
|
+
for (const shardId in results) {
|
|
422
|
+
if (results[shardId]) {
|
|
423
|
+
acquiredShardIds.push(shardId)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return acquiredShardIds
|
|
427
|
+
}))
|
|
455
428
|
},
|
|
456
429
|
|
|
457
430
|
mysql: () => {
|
|
@@ -471,31 +444,31 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
471
444
|
)
|
|
472
445
|
}
|
|
473
446
|
}
|
|
474
|
-
return
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
447
|
+
return (_address: string, shardIds: ReadonlyArray<string>) =>
|
|
448
|
+
useLockConn(Effect.fnUntraced(function*(conn, pid) {
|
|
449
|
+
const takenLocks = (yield* conn.executeValues(`SELECT ${allMySqlTakenLocks}`, []))[0] as Array<number | null>
|
|
450
|
+
const acquiredShardIds: Array<string> = []
|
|
451
|
+
const toAcquire: Array<string> = []
|
|
452
|
+
for (let i = 0; i < shardIds.length; i++) {
|
|
453
|
+
const shardId = shardIds[i]
|
|
454
|
+
const lockTakenBy = takenLocks[shardIdsIndex.get(shardId)!]
|
|
455
|
+
if (lockTakenBy === pid) {
|
|
456
|
+
acquiredShardIds.push(shardId)
|
|
457
|
+
} else if (shardIds.includes(shardId)) {
|
|
458
|
+
toAcquire.push(shardId)
|
|
459
|
+
}
|
|
486
460
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
return acquiredShardIds
|
|
490
|
-
}
|
|
491
|
-
const results = (yield* conn.executeValues(`SELECT ${mysqlLocks(toAcquire)}`, []))[0] as Array<number>
|
|
492
|
-
for (let i = 0; i < results.length; i++) {
|
|
493
|
-
if (results[i] === 1) {
|
|
494
|
-
acquiredShardIds.push(toAcquire[i])
|
|
461
|
+
if (toAcquire.length === 0) {
|
|
462
|
+
return acquiredShardIds
|
|
495
463
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
464
|
+
const results = (yield* conn.executeValues(`SELECT ${mysqlLocks(toAcquire)}`, []))[0] as Array<number>
|
|
465
|
+
for (let i = 0; i < results.length; i++) {
|
|
466
|
+
if (results[i] === 1) {
|
|
467
|
+
acquiredShardIds.push(toAcquire[i])
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return acquiredShardIds
|
|
471
|
+
}))
|
|
499
472
|
},
|
|
500
473
|
|
|
501
474
|
mssql: () => (address: string, shardIds: ReadonlyArray<string>) => {
|
|
@@ -647,8 +620,16 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
647
620
|
`.pipe(execWithLockConnValues, Effect.map((rows) => rows.map((row) => row[0] as string)))
|
|
648
621
|
})
|
|
649
622
|
|
|
623
|
+
// On failure, replace the connection ready at entry. If none was ready,
|
|
624
|
+
// retry only a failed rebuild, leaving pending or newly ready connections alone.
|
|
650
625
|
const withLockOperationDeadline = <A, E, R>(operation: Effect.Effect<A, E, R>) =>
|
|
651
|
-
|
|
626
|
+
Effect.suspend(() => {
|
|
627
|
+
const ready = lockConn?.getUnsafe()
|
|
628
|
+
return Effect.onError(withDeadline(operation), () => {
|
|
629
|
+
if (ready !== undefined) return rebuildLockConn(ready)
|
|
630
|
+
return lockConn?.getUnsafe() === undefined ? rebuildLockConn() : Effect.void
|
|
631
|
+
})
|
|
632
|
+
})
|
|
652
633
|
|
|
653
634
|
const releaseShard = sql.onDialectOrElse({
|
|
654
635
|
pg: () => {
|
|
@@ -656,11 +637,10 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
656
637
|
return (address: string, shardId: string) =>
|
|
657
638
|
sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`.pipe(execWithLockConn)
|
|
658
639
|
}
|
|
659
|
-
return
|
|
660
|
-
function*(
|
|
640
|
+
return (_address: string, shardId: string) =>
|
|
641
|
+
useLockConn(Effect.fnUntraced(function*(conn) {
|
|
661
642
|
const lockNum = lockNumbers.get(shardId)!
|
|
662
643
|
for (let i = 0; i < 5; i++) {
|
|
663
|
-
const [conn] = yield* lockConn!.await
|
|
664
644
|
yield* conn.executeRaw(`SELECT pg_advisory_unlock(${pgLockNamespace}, ${lockNum})`, [])
|
|
665
645
|
const takenLocks = yield* conn.executeValues(
|
|
666
646
|
`SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND granted = true AND classid = ${pgLockNamespaceOid} AND objid = ${lockNum} AND objsubid = 2 AND pid = pg_backend_pid()`,
|
|
@@ -668,23 +648,18 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
668
648
|
)
|
|
669
649
|
if (takenLocks.length === 0) return
|
|
670
650
|
}
|
|
671
|
-
const [conn] = yield* lockConn!.await
|
|
672
651
|
yield* conn.executeRaw(`SELECT pg_advisory_unlock_all()`, [])
|
|
673
|
-
}
|
|
674
|
-
onErrorRebuildLockConn,
|
|
675
|
-
Effect.asVoid
|
|
676
|
-
)
|
|
652
|
+
}))
|
|
677
653
|
},
|
|
678
654
|
mysql: () => {
|
|
679
655
|
if (disableAdvisoryLocks) {
|
|
680
656
|
return (address: string, shardId: string) =>
|
|
681
657
|
sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`.pipe(execWithLockConn)
|
|
682
658
|
}
|
|
683
|
-
return
|
|
684
|
-
function*(
|
|
659
|
+
return (_address: string, shardId: string) =>
|
|
660
|
+
useLockConn(Effect.fnUntraced(function*(conn, pid) {
|
|
685
661
|
const lockName = lockNames.get(shardId)!
|
|
686
662
|
while (true) {
|
|
687
|
-
const [conn, pid] = yield* lockConn!.await
|
|
688
663
|
yield* conn.executeRaw(`SELECT RELEASE_LOCK('${lockName}')`, [])
|
|
689
664
|
const takenLocks = yield* conn.executeValues(
|
|
690
665
|
`SELECT IS_USED_LOCK('${lockName}')`,
|
|
@@ -692,10 +667,7 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
692
667
|
)
|
|
693
668
|
if (takenLocks.length === 0 || takenLocks[0][0] !== pid) return
|
|
694
669
|
}
|
|
695
|
-
}
|
|
696
|
-
onErrorRebuildLockConn,
|
|
697
|
-
Effect.asVoid
|
|
698
|
-
)
|
|
670
|
+
}))
|
|
699
671
|
},
|
|
700
672
|
orElse: () => (address: string, shardId: string) =>
|
|
701
673
|
sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`
|