ruby-concurrency/concurrent-ruby · critical · Concurrent::ResourceLimitError
Too many writer threads
Error message
Too many writer threads
What it means
Concurrent::ReadWriteLock stores waiting writers in the middle bits of its state integer (WAITING_WRITER = 1 << 15, RUNNING_WRITER = 1 << 29); when the waiting-writer count saturates, acquire_write_lock raises Concurrent::ResourceLimitError. Waiting writers drain as soon as the lock frees, so saturation means writers are permanently stuck — classically a deadlock where a reader or the running writer never releases. Note the class documentation explicitly warns that acquiring the write lock while holding the read lock deadlocks this lock.
Source
Thrown at lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb:169
if waiting_writer?(c) && running_readers(c) == 1
@WriteLock.signal
end
break
end
end
true
end
# Acquire a write lock. Will block and wait for all active readers and writers.
#
# @return [Boolean] true if the lock is successfully acquired
#
# @raise [Concurrent::ResourceLimitError] if the maximum number of writers
# is exceeded.
def acquire_write_lock
while true
c = @Counter.value
raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)
if c == 0 # no readers OR writers running
# if we successfully swap the RUNNING_WRITER bit on, then we can go ahead
break if @Counter.compare_and_set(0, RUNNING_WRITER)
elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
while true
# Now we have successfully incremented, so no more readers will be able to increment
# (they will wait instead)
# However, readers OR writers could decrement right here, OR another writer could increment
@WriteLock.wait_until do
# So we have to do another check inside the synchronized section
# If a writer OR reader is running, then go to sleep
c = @Counter.value
!running_writer?(c) && !running_readers?(c)
end
# We just came out of a wait
# If we successfully turn the RUNNING_WRITER bit on with an atomic swap,View on GitHub (pinned to 0b88d5ff75)
Solutions
- Never take the write lock while holding the read lock on ReadWriteLock — take the write lock once for the whole read-modify-write, or use a design that does not need upgrading.
- Always release via `lock.with_write_lock { ... }` or begin/ensure pairing.
- When this error appears, dump all thread backtraces (Thread.list each backtrace) — the first stuck holder is the root cause, later queuers are victims.
- Keep write critical sections short and free of blocking IO so the waiting queue drains quickly.
Example fix
// before — read-then-write upgrade deadlocks this lock class
lock.with_read_lock do
lock.with_write_lock { cache.write(x) } # waits for its own read lock; writers pile up
end
// after — one exclusive acquisition for read-modify-write
lock.with_write_lock do
v = cache.read
cache.write(transform(v))
end Defensive patterns
Strategy: try-catch
Validate before calling
Thread.list.each { |t| next if t == Thread.current } # survey holders before piling on writers Try / catch
begin
lock.with_write_lock { persist(rows) }
rescue Concurrent::ResourceLimitError => e
Thread.list.each { |t| logger.error("#{t}: #{(t.backtrace || []).join(' | ')}") }
raise
end Prevention
- Never acquire the write lock while holding the read lock on the same ReadWriteLock — the class doc calls this out as deadlock.
- Use with_write_lock exclusively so release is guaranteed on every path.
- When one thread needs read plus write access, restructure to a single write acquisition instead of upgrading.
When it happens
Trigger: A thread holds the read lock and then tries to acquire the write lock on the same non-reentrant ReadWriteLock while other writers queue behind it; a writer dies or raises between manual acquire_write_lock and release_write_lock; read locks leaked so the running writer never finishes and waiting writers accumulate.
Common situations: Read-then-write upgrade patterns on a lock documented as non-upgradeable; skipping release on exception paths; background jobs piling onto a wedged lock until the bitfield fills.
Related errors
- Too many reader threads
- Too many reader threads
- Too many writer threads
- no block given
- Too many reader holds on this thread
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/5c3d0247cfa3c270.
Report an issue: GitHub.