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

  1. Switch to the boolean form and branch on it: `agent.await_for(5)` returns true/false instead of raising.
  2. Increase or remove the timeout (`await` waits indefinitely) when convergence is guaranteed.
  3. Profile the dispatched actions — move blocking I/O or heavy computation out of agent actions into tasks.
  4. 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

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

Related errors


AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21). Data as JSON: /api/errors/506508558adf300c. Report an issue: GitHub.