ruby-concurrency/concurrent-ruby · error · Concurrent::TimeoutError
Concurrent::TimeoutError
Error message
Concurrent::TimeoutError
What it means
`Agent#await_for!(timeout)` blocks until every action dispatched so far to the agent (from the current thread or nested by the agent) has completed, or the timeout elapses. The bang variant raises Concurrent::TimeoutError when `wait(timeout)` returns false — the action queue did not drain in time — while the non-bang `await_for(timeout)` returns false instead. Timeouts almost always mean the agent's executor is saturated, actions are slow or blocking, or the dispatch chain is longer than the budget assumes.
Source
Thrown at lib/concurrent-ruby/concurrent/agent.rb:378
# @return [Boolean] true if all actions complete before timeout else false
#
# @!macro agent_await_warning
def await_for(timeout)
wait(timeout.to_f)
end
# Blocks the current thread until all actions dispatched thus far, from this
# thread or nested by the Agent, have occurred, or the timeout (in seconds)
# has elapsed.
#
# @param [Float] timeout the maximum number of seconds to wait
# @return [Boolean] true if all actions complete before timeout
#
# @raise [Concurrent::TimeoutError] when timeout is reached
#
# @!macro agent_await_warning
def await_for!(timeout)
raise Concurrent::TimeoutError unless wait(timeout.to_f)
true
end
# Blocks the current thread until all actions dispatched thus far, from this
# thread or nested by the Agent, have occurred, or the timeout (in seconds)
# has elapsed. Will block indefinitely when timeout is nil or not given.
#
# Provided mainly for consistency with other classes in this library. Prefer
# the various `await` methods instead.
#
# @param [Float] timeout the maximum number of seconds to wait
# @return [Boolean] true if all actions complete before timeout else false
#
# @!macro agent_await_warning
def wait(timeout = nil)
latch = Concurrent::CountDownLatch.new(1)
enqueue_await_job(latch)
latch.wait(timeout)View on GitHub (pinned to 0b88d5ff75)
Solutions
- Switch to the boolean form and branch on it: `agent.await_for(5)` returns true/false instead of raising.
- Increase or remove the timeout (`await` waits indefinitely) when convergence is guaranteed.
- Profile the dispatched actions — move blocking I/O or heavy computation out of agent actions into tasks.
- Check for self-deadlock: an action (or its nested dispatches) must not wait on the same agent's completion.
Example fix
// before agent.await_for!(0.1) # raises Concurrent::TimeoutError under load // after unless agent.await_for(5) logger.warn 'agent did not settle within 5s' end
Defensive patterns
Strategy: try-catch
Try / catch
begin agent.await_for!(5) rescue Concurrent::TimeoutError # queue not drained; log, extend the budget, or degrade gracefully end
Prevention
- Prefer boolean await_for over the bang variant in production code.
- Keep agent actions short and non-blocking.
- Never wait on an agent from inside its own action chain.
- Size timeouts from measured action cost, not constants.
When it happens
Trigger: `agent.await_for!(0.1)` right after dispatching expensive actions; awaiting from inside the agent's own action chain in a way that only the agent can complete (self-deadlock); a starved shared executor (`Concurrent.global_fast_executor`) shared with other heavy work.
Common situations: Test suites asserting agent convergence with tight timeouts; bulk updates to one agent in a loop then awaiting; actions performing I/O or sleeping on the agent's thread pool.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- timeout must 0.0 or more
- agent is not failed
- unrecognized error mode
- no action given
- Concurrent::Agent::ValidationError
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/506508558adf300c.
Report an issue: GitHub.