{"record":{"id":"ed7a8caca8932831","repo":"ruby-concurrency/concurrent-ruby","slug":"too-many-reader-threads","errorCode":null,"errorMessage":"Too many reader threads","messagePattern":"Too many reader threads","errorType":"exception","errorClass":"Concurrent::ResourceLimitError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb","lineNumber":116,"sourceCode":"      acquire_write_lock\n      begin\n        yield\n      ensure\n        release_write_lock\n      end\n    end\n\n    # Acquire a read lock. If a write lock has been acquired will block until\n    # it is released. Will not block if other read locks have been acquired.\n    #\n    # @return [Boolean] true if the lock is successfully acquired\n    #\n    # @raise [Concurrent::ResourceLimitError] if the maximum number of readers\n    #   is exceeded.\n    def acquire_read_lock\n      while true\n        c = @Counter.value\n        raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)\n\n        # If a writer is waiting when we first queue up, we need to wait\n        if waiting_writer?(c)\n          @ReadLock.wait_until { !waiting_writer? }\n\n          # after a reader has waited once, they are allowed to \"barge\" ahead of waiting writers\n          # but if a writer is *running*, the reader still needs to wait (naturally)\n          while true\n            c = @Counter.value\n            if running_writer?(c)\n              @ReadLock.wait_until { !running_writer? }\n            else\n              return if @Counter.compare_and_set(c, c+1)\n            end\n          end\n        else\n          break if @Counter.compare_and_set(c, c+1)\n        end","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb#L98-L134","documentation":"Concurrent::ReadWriteLock packs its entire state into one AtomicFixnum: the low 15 bits count active readers, capping concurrent read locks at MAX_READERS = 32767. acquire_read_lock raises Concurrent::ResourceLimitError when that bitfield is full, protecting the packed representation. A healthy application almost never has 32k simultaneous readers, so this error usually indicates leaked read locks.","triggerScenarios":"Calling acquire_read_lock without a matching release_read_lock and then acquiring again (loop iterations, threads, or requests each add one to the count); an exception raised between manual acquire and release so release never runs; long-lived threads holding read locks forever.","commonSituations":"Using the manual acquire/release API instead of with_read_lock; early return or throw between acquire and release; a rescue-and-reraise path that skips release; treating a read lock as a registration mechanism and never releasing it.","solutions":["Switch to the block form `lock.with_read_lock { ... }` — its ensure clause always releases, even on exceptions.","If manual pairing is required, wrap it: acquire_read_lock; begin ... ensure release_read_lock end.","Audit code between acquire and release for early returns, throws, or re-raises that skip the release.","Instrument acquire/release (log a counter) to identify which caller leaks read locks.","If you legitimately exceed 32767 concurrent readers, shard the data across multiple ReadWriteLock instances."],"exampleFix":"// before\nlock.acquire_read_lock\nrows = read_table   # raise here leaks the lock forever\nlock.release_read_lock\n\n// after\nlock.with_read_lock { read_table }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  lock.with_read_lock { read_table }\nrescue Concurrent::ResourceLimitError => e\n  logger.fatal(\"read-lock saturation, probable read-lock leak: #{e.message}\")\n  raise\nend","preventionTips":["Never call acquire_read_lock without an ensure-guarded release_read_lock; prefer with_read_lock.","Do not hold read locks across long waits (IO, sleeps, queue pops) — readers accumulate.","Treat ResourceLimitError as a leak detector, not a transient condition; do not blindly retry."],"tags":["concurrent-ruby","read-write-lock","resource-limit","lock-leak"],"backgroundTag":"resource-limit-exceeded","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}