effect 4.0.0 → 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/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/Effect.d.ts +150 -22
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +11 -5
- 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/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/RcMap.d.ts.map +1 -1
- package/dist/RcMap.js +16 -1
- package/dist/RcMap.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 +508 -508
- 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 +11 -2
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +14 -7
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +21 -12
- 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/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 +63 -81
- 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/fromJsonSchemaDocument.js +11 -8
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +20 -14
- package/dist/rpc/RpcClient.js.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/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/Cache.ts +22 -16
- package/src/Cause.ts +1 -1
- package/src/Effect.ts +153 -22
- package/src/HashRing.ts +14 -5
- package/src/Pool.ts +28 -36
- package/src/RcMap.ts +26 -2
- 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 +18 -4
- package/src/cluster/Sharding.ts +24 -24
- package/src/cluster/SqlMessageStorage.ts +26 -13
- 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/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 +109 -111
- package/src/internal/rcRef.ts +83 -33
- package/src/internal/schema/fromJsonSchemaDocument.ts +5 -3
- package/src/rpc/RpcClient.ts +26 -22
- package/src/rpc/RpcServer.ts +2 -1
- package/src/rpc/Utils.ts +4 -10
- package/src/workflow/WorkflowEngine.ts +4 -0
|
@@ -411,7 +411,7 @@ export const make = Effect.gen(function*() {
|
|
|
411
411
|
// Replays reuse the request id but track a fresh set of awaits.
|
|
412
412
|
let currentRun: {
|
|
413
413
|
readonly request: Entity.Request<any>
|
|
414
|
-
readonly
|
|
414
|
+
readonly instance: WorkflowEngine.WorkflowInstance["Service"]
|
|
415
415
|
} | undefined
|
|
416
416
|
// Concurrent wakes share one wait for the current run to publish its reply.
|
|
417
417
|
const resumeGate = Semaphore.makeUnsafe(1)
|
|
@@ -428,7 +428,7 @@ export const make = Effect.gen(function*() {
|
|
|
428
428
|
return {
|
|
429
429
|
run: (request: Entity.Request<any>) => {
|
|
430
430
|
const instance = WorkflowEngine.WorkflowInstance.initial(workflow, executionId)
|
|
431
|
-
currentRun = { request,
|
|
431
|
+
currentRun = { request, instance }
|
|
432
432
|
const parent = (request.payload as any)[payloadParentKey] as
|
|
433
433
|
| { workflowName: string; executionId: string }
|
|
434
434
|
| undefined
|
|
@@ -522,9 +522,13 @@ export const make = Effect.gen(function*() {
|
|
|
522
522
|
// registered yet, a later await will see the result just stored.
|
|
523
523
|
// Check when the wake runs and skip preemption too: interrupting
|
|
524
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.
|
|
525
527
|
const wake = Effect.suspend(() =>
|
|
526
|
-
currentRun && !currentRun.awaitedDeferreds.has(payload.name)
|
|
527
|
-
?
|
|
528
|
+
currentRun && !currentRun.instance.awaitedDeferreds.has(payload.name)
|
|
529
|
+
? currentRun.instance.completedDeferreds.has(payload.name)
|
|
530
|
+
? Effect.void
|
|
531
|
+
: ensureSuccess(resume(workflow, executionId))
|
|
528
532
|
: deferredState.deferredDone(executionId, payload.name).pipe(Effect.andThen(resumeCurrentRun))
|
|
529
533
|
)
|
|
530
534
|
// An asynchronous reply releases the RPC concurrency permit while
|
|
@@ -703,6 +707,16 @@ export const make = Effect.gen(function*() {
|
|
|
703
707
|
|
|
704
708
|
deferredDone: Effect.fnUntraced(
|
|
705
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
|
+
}
|
|
706
720
|
const workflow = workflows.get(workflowName)
|
|
707
721
|
if (workflow) {
|
|
708
722
|
return yield* Effect.orDie(sendDiscard({
|
package/src/cluster/Sharding.ts
CHANGED
|
@@ -549,30 +549,27 @@ const make = Effect.gen(function*() {
|
|
|
549
549
|
return Effect.logInfo("Shard lock storage has recovered")
|
|
550
550
|
})
|
|
551
551
|
|
|
552
|
-
const refreshShardLocks = Effect.
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (!acquired.includes(shardId)) {
|
|
561
|
-
MutableHashSet.remove(acquiredShards, shardId)
|
|
562
|
-
MutableHashSet.add(releasingShards, shardId)
|
|
563
|
-
}
|
|
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)
|
|
564
560
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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)
|
|
571
567
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
568
|
+
}
|
|
569
|
+
if (MutableHashSet.size(releasingShards) > 0) {
|
|
570
|
+
yield* activeShardsLatch.open
|
|
571
|
+
}
|
|
572
|
+
}).pipe(
|
|
576
573
|
Effect.retry({
|
|
577
574
|
times: 5,
|
|
578
575
|
schedule: Schedule.spaced(50)
|
|
@@ -1462,8 +1459,10 @@ const make = Effect.gen(function*() {
|
|
|
1462
1459
|
// for durable messages, we ignore interrupts on shutdown or as a
|
|
1463
1460
|
// result of a shard being resassigned
|
|
1464
1461
|
const caller = Context.getOption(entry.context, CurrentAddress).valueOrUndefined
|
|
1462
|
+
const singletonShard = Context.getOption(entry.context, SingletonShardTag).valueOrUndefined
|
|
1465
1463
|
const isTransientInterrupt = MutableRef.get(isShutdown) ||
|
|
1466
|
-
(caller !== undefined && ActiveTeardown.isActive(caller))
|
|
1464
|
+
(caller !== undefined && ActiveTeardown.isActive(caller)) ||
|
|
1465
|
+
(singletonShard !== undefined && ActiveTeardown.isShardActive(singletonShard))
|
|
1467
1466
|
if (isTransientInterrupt && Context.get(entry.message.annotations, Persisted)) {
|
|
1468
1467
|
return Effect.void
|
|
1469
1468
|
}
|
|
@@ -1592,7 +1591,7 @@ const make = Effect.gen(function*() {
|
|
|
1592
1591
|
Effect.andThen(Effect.never),
|
|
1593
1592
|
Effect.scoped,
|
|
1594
1593
|
Effect.provideService(CurrentLogAnnotations, {}),
|
|
1595
|
-
Effect.provideContext(services),
|
|
1594
|
+
Effect.provideContext(Context.add(services, SingletonShardTag, address.shardId)),
|
|
1596
1595
|
Effect.orDie,
|
|
1597
1596
|
Effect.interruptible
|
|
1598
1597
|
) as Effect.Effect<never>
|
|
@@ -1855,3 +1854,4 @@ export const layer: Layer.Layer<
|
|
|
1855
1854
|
// Utilities
|
|
1856
1855
|
|
|
1857
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(() => {
|
|
@@ -716,11 +739,7 @@ export const makeEncoded: (options?: {
|
|
|
716
739
|
resetAddresses: (addresses) =>
|
|
717
740
|
addresses.length === 0
|
|
718
741
|
? Effect.void
|
|
719
|
-
: sql`
|
|
720
|
-
UPDATE ${messagesTableSql}
|
|
721
|
-
SET last_read = NULL
|
|
722
|
-
WHERE processed = ${sqlFalse}
|
|
723
|
-
AND (${
|
|
742
|
+
: resetUnprocessed(sql`(${
|
|
724
743
|
sql.or(
|
|
725
744
|
groupAddresses(addresses).map(
|
|
726
745
|
(group) =>
|
|
@@ -731,8 +750,7 @@ export const makeEncoded: (options?: {
|
|
|
731
750
|
])
|
|
732
751
|
)
|
|
733
752
|
)
|
|
734
|
-
})
|
|
735
|
-
`.pipe(
|
|
753
|
+
})`).pipe(
|
|
736
754
|
Effect.asVoid,
|
|
737
755
|
PersistenceError.refail,
|
|
738
756
|
withTracerDisabled
|
|
@@ -761,12 +779,7 @@ export const makeEncoded: (options?: {
|
|
|
761
779
|
),
|
|
762
780
|
|
|
763
781
|
resetShards: (shardIds) =>
|
|
764
|
-
sql`
|
|
765
|
-
UPDATE ${messagesTableSql}
|
|
766
|
-
SET last_read = NULL
|
|
767
|
-
WHERE processed = ${sqlFalse}
|
|
768
|
-
AND shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})
|
|
769
|
-
`.pipe(
|
|
782
|
+
resetUnprocessed(sql`shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})`).pipe(
|
|
770
783
|
Effect.asVoid,
|
|
771
784
|
PersistenceError.refail,
|
|
772
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}`
|