Skip to content

Fix recursive-mutex deadlock in Library#sync_catalog - #1220

Merged
castwide merged 1 commit into
castwide:masterfrom
apiology:fix-sync-catalog-recursive-mutex-deadlock
Aug 3, 2026
Merged

Fix recursive-mutex deadlock in Library#sync_catalog#1220
castwide merged 1 commit into
castwide:masterfrom
apiology:fix-sync-catalog-recursive-mutex-deadlock

Conversation

@apiology

Copy link
Copy Markdown
Contributor

Summary

Fixes #1111 (ThreadError: deadlock; recursive locking).

Library#cache_next_gemspec's "already being processed" branch enqueues
the gemspec and, if other gemspecs are still pending, recurses to try
the next one. It did this by calling sync_catalog again — but
cache_next_gemspec is only ever reached from inside sync_catalog's
own mutex.synchronize block (either directly, or transitively via
this same recursive path on the same thread), so re-entering
sync_catalog tries to re-lock a non-reentrant Thread::Mutex the
current thread already holds, raising the deadlock error from #1111's
stack trace exactly.

The fix recurses into cache_next_gemspec directly instead of through
sync_catalog, since the mutex is already held for the entire call
chain by the time this branch runs.

This bug predates the pin-caching rework and its revert (#1180) — it
was introduced in #990 and is independent of that whole saga, but it's
one of the "deadlocked threads" issues mentioned in #1180's PR
description as motivation for reverting, so I wanted to get it fixed
and out of the way on its own before looking at anything else in that
area.

Test plan

  • Added a regression spec (spec/library_spec.rb) that stubs
    Yardoc.processing? to force the recursive branch with multiple
    pending gemspecs; it reproduces the exact deadlock on current
    master and passes after the fix.
  • bundle exec rspec — full suite green (2 pre-existing, unrelated
    failures in rbs_map/conversions_spec.rb / activesupport_concern_spec.rb
    reproduce identically on master without this change).
  • bundle exec rubocop clean on changed files.

Library#cache_next_gemspec's "already being processed" branch called
sync_catalog to try the next cacheable gemspec, but that method is only
ever reached while already inside sync_catalog's own mutex.synchronize
block (directly, or via this same recursive path), so re-entering it
re-locks a non-reentrant Thread::Mutex the current thread already
holds, raising ThreadError: deadlock; recursive locking.

Recurse into cache_next_gemspec directly instead, since the mutex is
already held for the whole call chain.

Fixes castwide#1111
@apiology
apiology marked this pull request as ready for review July 29, 2026 12:31
@apiology
apiology force-pushed the fix-sync-catalog-recursive-mutex-deadlock branch from fd3a519 to fc77012 Compare July 31, 2026 11:49
@apiology
apiology marked this pull request as draft August 2, 2026 14:43
@apiology
apiology marked this pull request as ready for review August 2, 2026 17:19
@apiology

apiology commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@castwide Ready for review

@castwide

castwide commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Tested locally, looks good 👍

@castwide
castwide merged commit 0e0ddeb into castwide:master Aug 3, 2026
50 of 56 checks passed
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.

Error processing request: [ThreadError] deadlock; recursive locking

2 participants