{"record":{"id":"becae73adebfbd5f","repo":"ruby-concurrency/concurrent-ruby","slug":"too-many-writer-threads-becae7","errorCode":null,"errorMessage":"Too many writer threads","messagePattern":"Too many writer threads","errorType":"exception","errorClass":"Concurrent::ResourceLimitError","httpStatus":null,"severity":"critical","filePath":"lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb","lineNumber":273,"sourceCode":"      true\n    end\n\n    # Acquire a write lock. Will block and wait for all active readers and writers.\n    #\n    # @return [Boolean] true if the lock is successfully acquired\n    #\n    # @raise [Concurrent::ResourceLimitError] if the maximum number of writers\n    #   is exceeded.\n    def acquire_write_lock\n      if (held = @HeldCount.value) >= WRITE_LOCK_HELD\n        # if we already have a write (exclusive) lock, there's no need to wait\n        @HeldCount.value = held + WRITE_LOCK_HELD\n        return true\n      end\n\n      while true\n        c = @Counter.value\n        raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)\n\n        # To go ahead and take the lock without waiting, there must be no writer\n        #   running right now, AND no writers who came before us still waiting to\n        #   acquire the lock\n        # Additionally, if any read locks have been taken, we must hold all of them\n        if held > 0 && @Counter.compare_and_set(1, c+RUNNING_WRITER)\n          # If we are the only one reader and successfully swap the RUNNING_WRITER bit on, then we can go ahead\n          @HeldCount.value = held + WRITE_LOCK_HELD\n          return true\n        elsif @Counter.compare_and_set(c, c+WAITING_WRITER)\n          while true\n            # Now we have successfully incremented, so no more readers will be able to increment\n            #   (they will wait instead)\n            # However, readers OR writers could decrement right here\n            @WriteQueue.synchronize do\n              # So we have to do another check inside the synchronized section\n              # If a writer OR another reader is running, then go to sleep\n              c = @Counter.value","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb#L255-L291","documentation":"ReentrantReadWriteLock reserves 14 middle bits of its @Counter for waiting writers; when that field saturates at MAX_WRITERS, acquire_write_lock raises Concurrent::ResourceLimitError. Waiting writers only drain when the running writer releases and readers clear, so saturation means the lock is wedged — a writer that never released, or readers whose leak prevents any writer from running.","triggerScenarios":"Manual acquire_write_lock without begin/ensure release where an exception kills the holder while others queue; a leaked read lock preventing queued writers from ever proceeding; heavy write traffic funneled through a lock whose current writer is deadlocked.","commonSituations":"Write critical sections containing early throws; mixing manual and block lock APIs; long write batches under a single lock while background jobs pile up behind it.","solutions":["Use with_write_lock { } for every write critical section so release is guaranteed.","Ensure read locks on the same data are released — otherwise queued writers never run (see the reader limit errors).","When the error appears, dump all thread backtraces; the first stuck holder is the cause, later queuers are symptoms.","Keep write critical sections short and free of blocking IO."],"exampleFix":"// before — holder dies, every other writer queues forever\nlock.acquire_write_lock\npersist(rows)      # raises, release never runs\nlock.release_write_lock\n\n// after\nlock.with_write_lock { persist(rows) }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  lock.with_write_lock { persist(rows) }\nrescue Concurrent::ResourceLimitError => e\n  Thread.list.each { |t| logger.error(\"#{t}: #{(t.backtrace || []).join(' | ')}\") }\n  raise\nend","preventionTips":["Use with_write_lock for every write section so release is guaranteed.","Make sure read locks on the same data are released, or no writer will ever run.","Keep write sections short and free of blocking IO so the waiting-writer queue drains."],"tags":["concurrent-ruby","reentrant-lock","deadlock","resource-limit"],"backgroundTag":"resource-limit-exceeded","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}