ruby-concurrency/concurrent-ruby · error · ArgumentError
can't set a timeout if non_block is enabled
Error message
can't set a timeout if non_block is enabled
What it means
`Concurrent::TimeoutQueue#pop(non_block = false, timeout: nil)` (RubyTimeoutQueue backs TimeoutQueue on Ruby < 3.2) supports three modes: blocking pop, non-blocking `pop(true)` (raises ThreadError 'queue empty' when empty), and timed pop via the `timeout:` keyword (returns nil on expiry). A non-blocking call cannot honour a timeout, so passing both `non_block = true` and `timeout:` raises ArgumentError before any dequeue is attempted.
Source
Thrown at lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb:28
super(*args)
@mutex = Mutex.new
@cond_var = ConditionVariable.new
end
def push(obj)
@mutex.synchronize do
super(obj)
@cond_var.signal
end
end
alias_method :enq, :push
alias_method :<<, :push
def pop(non_block = false, timeout: nil)
if non_block && timeout
raise ArgumentError, "can't set a timeout if non_block is enabled"
end
if non_block
super(true)
elsif timeout
@mutex.synchronize do
deadline = Concurrent.monotonic_time + timeout
while (now = Concurrent.monotonic_time) < deadline && empty?
@cond_var.wait(@mutex, deadline - now)
end
begin
return super(true)
rescue ThreadError
# still empty
nil
end
end
elseView on GitHub (pinned to 0b88d5ff75)
Solutions
- Pick one mode: `queue.pop(timeout: 5)` for a bounded wait, or `queue.pop(true)` for try-now.
- In adapters, translate explicitly: `timeout ? q.pop(timeout: timeout) : q.pop(non_block)`.
- Model the three modes (block / try / timed) as separate methods in your wrapper so callers cannot combine flags.
Example fix
// before item = queue.pop(true, timeout: 5) # ArgumentError // after item = queue.pop(timeout: 5) # wait up to 5s, nil on timeout // or item = queue.pop(true) # non-blocking, ThreadError when empty
Defensive patterns
Strategy: validation
Validate before calling
def pop_from(queue, non_block: false, timeout: nil) raise ArgumentError, 'non_block and timeout are mutually exclusive' if non_block && timeout timeout ? queue.pop(timeout: timeout) : queue.pop(non_block) end
Try / catch
begin queue.pop(true, timeout: t) rescue ArgumentError queue.pop(timeout: t) # degrade to the timed form end
Prevention
- Model three modes explicitly — blocking, try-now, timed — and never combine try with timed.
- Normalize pop signatures inside one queue adapter.
- Remember: pop(true) on empty raises ThreadError; timed pop returns nil on expiry.
When it happens
Trigger: `queue.pop(true, timeout: 5)`; adapters porting ::Queue's `pop(non_block)` signature that also forward a `timeout:` keyword; wrappers that always specify `timeout:` and pass through the caller's `non_block` flag. Legal: `pop`, `pop(true)`, `pop(timeout: 5)`, `pop(false, timeout: 5)`.
Common situations: Porting code between ::Queue and Concurrent::TimeoutQueue; generic queue gateway objects that combine both options; cargo-culted `pop(true, timeout: x)` copied from another library's API where the combination is legal.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- unbuffered channels cannot have a capacity
- capacity must be at least 1 for this buffer type
- no block given
- size must be greater than 0
- invalid action
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/a36e1360d3083e2b.
Report an issue: GitHub.