Skip to content

Prevent SQL runner lock refreshes from hanging#6516

Open
tim-smart wants to merge 10 commits into
mainfrom
t3code/test-v4-issue-6508
Open

Prevent SQL runner lock refreshes from hanging#6516
tim-smart wants to merge 10 commits into
mainfrom
t3code/test-v4-issue-6508

Conversation

@tim-smart

@tim-smart tim-smart commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Run SQL runner lock refreshes in a detached fiber so stalled reserved connections cannot block interruption.
  • Bound refresh operations by the configured shard lock refresh interval.
  • Add regression coverage for an unresponsive reserved connection.

Testing

  • Not run (test results were not provided).
  • Added a SqlRunnerStorage test using TestClock to verify refresh completes before the shard lock expires.

Summary by CodeRabbit

  • Bug Fixes
    • Bounded SQL shard-lock refresh/acquire/release operations so unresponsive reserved connections can’t stall; affected shard activity is interrupted and lock connections are rebuilt when needed.
    • Improved shard-lock health handling: shard acquisition pauses and storage refresh shifts to safer probing while lock health is uncertain or unhealthy, then shards are reacquired after recovery.
    • Added “force” shard interruption support for faster shutdown under failure.
  • Tests
    • Added failover/blackhole scenarios covering refresh timeouts, interruption behavior, and recovery with subsequent shard reacquisition.

- Simulate a partitioned SQL connection and verify refresh completes before the shard lock expires
- Detach refresh work and bound fiber joins by the shard lock refresh interval
@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog Jul 21, 2026
@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f542e4c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Shard locking now uses lease-safe deadlines and explicit health tracking. Unresponsive SQL connections trigger bounded rebuild handling; unhealthy storage interrupts affected entities, pauses acquisition, releases uncertain ownership, and probes for recovery before normal reacquisition. Tests cover SQL blackholes, failover, recovery, and graceful reassignment.

Changes

Shard lock failover

Layer / File(s) Summary
Bound lock storage operations
packages/effect/src/unstable/cluster/internal/shardLock.ts, packages/effect/src/unstable/cluster/SqlRunnerStorage.ts, packages/effect/src/unstable/cluster/ShardingConfig.ts
Lock operations and connection rebuilds use bounded fibers, timeouts, centralized error recovery, and a lease-safe effective interval.
Force entity shutdown during failover
packages/effect/src/unstable/cluster/internal/entityManager.ts
Shard interruption supports forced shutdown through separate normal and force latches.
Coordinate unhealthy lock storage
packages/effect/src/unstable/cluster/Sharding.ts
Sharding tracks lock health, pauses acquisition, force-releases uncertain shards, probes storage, and reacquires shards after recovery.
Validate failover and stalled connections
packages/effect/test/cluster/Sharding.test.ts, packages/platform-node/test/cluster/SqlRunnerStorage.test.ts, .changeset/slow-spiders-refresh.md
Tests simulate blackholed storage and reserved connections, verify interruption and recovery behavior, and document the patch release.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Sharding
  participant RunnerStorage
  participant SqlConnection
  participant EntityManager
  Sharding->>RunnerStorage: refresh shard locks
  RunnerStorage->>SqlConnection: execute bounded lock SQL
  SqlConnection-->>RunnerStorage: timeout or failure
  RunnerStorage-->>Sharding: lock storage unhealthy
  Sharding->>EntityManager: force interrupt affected shards
  Sharding->>RunnerStorage: release uncertain shards
  Sharding->>RunnerStorage: probe and reacquire after recovery
Loading

Possibly related PRs

  • Effect-TS/effect#6502: Modifies PostgreSQL advisory-lock acquisition and release logic in the same shard-lock code paths.

Suggested labels: bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: bounding SQL runner lock refreshes to prevent hangs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the bug Something isn't working label Jul 21, 2026
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

File Name Current Size Previous Size Difference
basic.ts 6.64 KB 6.64 KB 0.00 KB (0.00%)
batching.ts 9.42 KB 9.42 KB 0.00 KB (0.00%)
brand.ts 6.20 KB 6.20 KB 0.00 KB (0.00%)
cache.ts 10.13 KB 10.13 KB 0.00 KB (0.00%)
config.ts 19.08 KB 19.08 KB 0.00 KB (0.00%)
differ.ts 18.41 KB 18.41 KB 0.00 KB (0.00%)
http-client.ts 20.73 KB 20.73 KB 0.00 KB (0.00%)
logger.ts 10.30 KB 10.30 KB 0.00 KB (0.00%)
metric.ts 8.56 KB 8.56 KB 0.00 KB (0.00%)
optic.ts 7.41 KB 7.41 KB 0.00 KB (0.00%)
pubsub.ts 14.17 KB 14.17 KB 0.00 KB (0.00%)
queue.ts 11.10 KB 11.10 KB 0.00 KB (0.00%)
schedule.ts 10.28 KB 10.28 KB 0.00 KB (0.00%)
schema-class.ts 18.16 KB 18.16 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 27.31 KB 27.31 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 24.16 KB 24.16 KB 0.00 KB (0.00%)
schema-string-transformation.ts 12.68 KB 12.68 KB 0.00 KB (0.00%)
schema-string.ts 10.36 KB 10.36 KB 0.00 KB (0.00%)
schema-template-literal.ts 14.38 KB 14.38 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 20.94 KB 20.94 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 23.42 KB 23.42 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 18.30 KB 18.30 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 18.01 KB 18.01 KB 0.00 KB (0.00%)
schema-toFormatter.ts 17.86 KB 17.86 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 21.49 KB 21.49 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 18.55 KB 18.55 KB 0.00 KB (0.00%)
schema.ts 17.43 KB 17.43 KB 0.00 KB (0.00%)
stm.ts 12.07 KB 12.07 KB 0.00 KB (0.00%)
stream.ts 9.36 KB 9.36 KB 0.00 KB (0.00%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/platform-node/test/cluster/SqlRunnerStorage.test.ts`:
- Line 49: Update the TestClock.adjust call in the relevant SqlRunnerStorage
test to advance only just beyond the 100ms refresh interval, such as 101ms,
while remaining below the 1000ms lock expiration boundary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 89399661-80d2-4c72-b172-a5982e6075a7

📥 Commits

Reviewing files that changed from the base of the PR and between dd44624 and b15cbb5.

📒 Files selected for processing (3)
  • .changeset/slow-spiders-refresh.md
  • packages/effect/src/unstable/cluster/SqlRunnerStorage.ts
  • packages/platform-node/test/cluster/SqlRunnerStorage.test.ts

Comment thread packages/platform-node/test/cluster/SqlRunnerStorage.test.ts Outdated
@github-project-automation github-project-automation Bot moved this from Discussion Ongoing to Waiting on Author in PR Backlog Jul 21, 2026
- Bound SQL shard-lock operations and rebuild stalled connections
- Interrupt entities when lock ownership is uncertain
- Reacquire assigned shards with fresh entities after storage recovers

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/effect/src/unstable/cluster/SqlRunnerStorage.ts (1)

590-608: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer Effect.fnUntraced for this Effect-returning helper.

withLockOperationDeadline is a function whose body is only an Effect.gen. Per the repo's Effect conventions this should be defined with Effect.fnUntraced.

♻️ Suggested conversion
-  const withLockOperationDeadline = <A, E, R>(operation: Effect.Effect<A, E, R>) =>
-    Effect.gen(function*() {
-      const fiber = yield* Effect.forkIn(operation, layerScope, { startImmediately: true })
-      return yield* Fiber.join(fiber).pipe(
+  const withLockOperationDeadline = Effect.fnUntraced(function*<A, E, R>(operation: Effect.Effect<A, E, R>) {
+      const fiber = yield* Effect.forkIn(operation, layerScope, { startImmediately: true })
+      return yield* Fiber.join(fiber).pipe(
         Effect.timeout(lockOperationInterval),
         Effect.ensuring(
           Fiber.interrupt(fiber).pipe(
             Effect.forkIn(layerScope, { startImmediately: true }),
             Effect.asVoid
           )
         ),
         Effect.tap(() =>
           Effect.sync(() => {
             lockConnRebuildNeeded = true
           })
         ),
         Effect.onError(rebuildLockConn)
       )
-    })
+  })
Based on coding guidelines: "prefer `Effect.fnUntraced` over functions that only return `Effect.gen`".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/src/unstable/cluster/SqlRunnerStorage.ts` around lines 590 -
608, Convert the withLockOperationDeadline helper to use Effect.fnUntraced,
wrapping its existing Effect.gen implementation without changing the operation
timeout, interruption, rebuild, or error-handling behavior.

Source: Coding guidelines

packages/effect/test/cluster/Sharding.test.ts (1)

680-680: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use assert from @effect/vitest instead of Vitest's expect.

Every other assertion in these tests uses assert.*; this line is the lone expect(...).

♻️ Suggested change
-        expect(yield* client.GetUserVolatile({ id: 2 })).toEqual(new User({ id: 2, name: "User 2" }))
+        assert.deepStrictEqual(yield* client.GetUserVolatile({ id: 2 }), new User({ id: 2, name: "User 2" }))
Based on coding guidelines: "do not use `Effect.runSync` or Vitest's `expect`; use `assert` from `@effect/vitest`".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/cluster/Sharding.test.ts` at line 680, Replace the lone
Vitest expect assertion in the GetUserVolatile test with the corresponding
assert API from `@effect/vitest`, preserving the existing expected User value and
assertion behavior. Ensure this test continues to follow the surrounding
assert.* convention and does not use Vitest’s expect.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/effect/src/unstable/cluster/SqlRunnerStorage.ts`:
- Around line 590-608: Convert the withLockOperationDeadline helper to use
Effect.fnUntraced, wrapping its existing Effect.gen implementation without
changing the operation timeout, interruption, rebuild, or error-handling
behavior.

In `@packages/effect/test/cluster/Sharding.test.ts`:
- Line 680: Replace the lone Vitest expect assertion in the GetUserVolatile test
with the corresponding assert API from `@effect/vitest`, preserving the existing
expected User value and assertion behavior. Ensure this test continues to follow
the surrounding assert.* convention and does not use Vitest’s expect.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8c3d7150-df19-4cdd-a864-cbe604709739

📥 Commits

Reviewing files that changed from the base of the PR and between ab082ca and eaaaef9.

📒 Files selected for processing (8)
  • .changeset/slow-spiders-refresh.md
  • packages/effect/src/unstable/cluster/Sharding.ts
  • packages/effect/src/unstable/cluster/ShardingConfig.ts
  • packages/effect/src/unstable/cluster/SqlRunnerStorage.ts
  • packages/effect/src/unstable/cluster/internal/entityManager.ts
  • packages/effect/src/unstable/cluster/internal/shardLock.ts
  • packages/effect/test/cluster/Sharding.test.ts
  • packages/platform-node/test/cluster/SqlRunnerStorage.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/slow-spiders-refresh.md
  • packages/platform-node/test/cluster/SqlRunnerStorage.test.ts

- Define the lock deadline helper with Effect.fnUntraced
- Replace the remaining Vitest expectation with an Effect assertion
- Clarify that the deadline applies while joining cleanup fibers
@IMax153 IMax153 added the 4.0 label Jul 22, 2026
- Reuse latch state and retry logic across shard release paths
- Avoid interrupting completed SQL lock operations
tim-smart and others added 3 commits July 25, 2026 17:03
- Skip shard acquisition while a forced releaseAll is pending
- Cover reacquisition during a pending forced release

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rebuilds were only re-armed by a successful lock operation, so once a
rebuild succeeded the runner could not rebuild again while every
operation kept failing - exactly the unresponsive-connection case.

Track a connection generation instead: a failing operation schedules a
rebuild unless the connection it ran on has already been replaced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rebuilding the reserved lock connection wrapped `rebuildUnsafe` in a plain
`Effect.timeout`, which waits for the timed-out effect to finish interrupting.
The rebuild starts by closing the previous scope, so a driver that stalls
releasing the unresponsive connection kept the rebuild fiber alive past its
deadline, left `lockConnRebuilding` set forever, and disabled every later
rebuild.

Reuse the fork-and-timeout-the-join pattern already used for lock operations,
so the stalled release finishes detached and the flag is always reset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 bug Something isn't working

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

2 participants