Skip to content

Serialize OpenBucket and stop expiration before closing the bucket - #100

Merged
bbrks merged 2 commits into
mainfrom
openbucket-register-race
Oct 2, 2026
Merged

bbrks merged 2 commits into
mainfrom
openbucket-register-race

Conversation

@torcolvin

@torcolvin torcolvin commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Two OpenBucket calls for the same bucket could both miss the cache, so both opened SQLite and both registered. This caused these bugs:

  • The call that lost the race called Close, which lowered the registry count for the copy that it returned.
  • A CreateNew call that lost the race returned the other call's bucket with no error, instead of fs.ErrExist.
  • _scheduleExpiration ran without the expiry manager lock, while writes (feeds.go:179) scheduled expiration at the same time. This was a data race.

Fix: OpenBucket now holds openLock on the registry from getCachedBucket until it registers the bucket. Only one call opens a bucket, so no call can lose the race. The bucket schedules expiration before it is registered, and it takes the expiry manager lock to do so.

Close and expiration also locked two mutexes in opposite orders, so they could deadlock:

  • close() locked bucket.mutex, then expManager.mutex.
  • runExpiry locked expManager.mutex, then bucket.mutex.

Fix:

  • close() now stops the expiry manager before it locks the bucket.
  • stop() waits for a running expiration to finish.
  • runExpiry holds a separate runMutex, so no expiration can start after stop().

A new CI job builds with -tags deadlock. That build uses go-deadlock mutexes, and the job fails on any lock order inversion, even when the goroutines do not deadlock during the test.

Tests:

  • TestConcurrentOpenBucket checks that concurrent reopens open the database one time.
  • TestConcurrentCreateNewBucket checks that one CreateNew call succeeds and the others get fs.ErrExist.
  • TestCloseDuringExpiration checks that Close waits for a running expiration. The old code deadlocked in this test.

I ran the rosmar tests 200 times with -race, and 150 times with -race -tags deadlock. I also ran the sync gateway tests with this set of changes, with and without -race.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Resolve the lock-order deadlock and make the registration-race test deterministic.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Fixes OpenBucket reference counting and expiration scheduling during concurrent registration races.

Changes:

  • Corrects loser-bucket cleanup without unregistering.
  • Synchronizes expiration scheduling.
  • Adds concurrent-open regression coverage.
File Summary
bucket.go Handles registration-race losers safely.
bucket_registry_test.go Adds concurrent bucket-opening coverage; the race path should be made deterministic.
bucket_api.go Adds expiration scheduling synchronization; a lock-order deadlock must be resolved.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread bucket_api.go
Comment thread bucket_registry_test.go Outdated
@torcolvin
torcolvin force-pushed the openbucket-register-race branch from 55cd851 to 283f14b Compare September 25, 2026 16:47
@torcolvin torcolvin changed the title Fix OpenBucket reference count when it loses the registration race Serialize OpenBucket and stop expiration before closing the bucket Sep 25, 2026
@torcolvin
torcolvin requested a lite review from Copilot September 25, 2026 16:48
Comment thread .github/workflows/ci.yml Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Fix CI test-status propagation and address unnecessary serialization of unrelated bucket opens.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Comment thread .github/workflows/ci.yml
torcolvin and others added 2 commits September 28, 2026 12:57
Hold a registry open lock from the cache lookup until registration, so
concurrent OpenBucket calls never open the same bucket twice. A CreateNew
call that loses now gets fs.ErrExist.

Stop the expiry manager before locking the bucket on close, and wait for a
running expiration to finish. The expiration no longer holds the expiry
manager mutex while it locks the bucket, which removes a lock order
inversion with close.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Build with the deadlock tag to replace the package mutexes with
go-deadlock mutexes. The job fails on a lock order inversion, even when
the test goroutines do not interleave to deadlock.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@torcolvin
torcolvin force-pushed the openbucket-register-race branch from 0ddc53c to 9471412 Compare September 28, 2026 16:58
@torcolvin
torcolvin requested a review from bbrks September 28, 2026 16:59
@bbrks
bbrks merged commit 86035c9 into main Oct 2, 2026
12 checks passed
@bbrks
bbrks deleted the openbucket-register-race branch October 2, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants