ruby-concurrency/concurrent-ruby · error · Concurrent::Error

it is not rejected

Error message

it is not rejected

What it means

Future#exception (promises.rb:1011-1016) exists so a rejected future can be used with raise (Ruby's raise calls obj.exception). It requires the future to be rejected; when called on a pending or fulfilled future it raises Concurrent::Error 'it is not rejected' (promises.rb:1015). It is a state precondition on the future, not a general accessor for the result.

Source

Thrown at lib/concurrent-ruby/concurrent/promises.rb:1015

      # @raise [Exception] {#reason} on rejection
      def value!(timeout = nil, timeout_value = nil)
        if wait_until_resolved! timeout
          internal_state.value
        else
          timeout_value
        end
      end

      # Allows rejected Future to be risen with `raise` method.
      # If the reason is not an exception `Runtime.new(reason)` is returned.
      #
      # @example
      #   raise Promises.rejected_future(StandardError.new("boom"))
      #   raise Promises.rejected_future("or just boom")
      # @raise [Concurrent::Error] when raising not rejected future
      # @return [Exception]
      def exception(*args)
        raise Concurrent::Error, 'it is not rejected' unless rejected?
        raise ArgumentError unless args.size <= 1
        reason = Array(internal_state.reason).flatten.compact
        callsites = SET_BACKTRACE_LOCATIONS_SUPPORTED ? caller_locations : caller
        if reason.size > 1
          ex = Concurrent::MultipleErrors.new reason
          ex.set_backtrace(callsites)
          ex
        else
          ex = if reason[0].respond_to? :exception
                 reason[0].exception(*args)
               else
                 RuntimeError.new(reason[0]).exception(*args)
               end
          if SET_BACKTRACE_LOCATIONS_SUPPORTED && (locations = ex.backtrace_locations)
            ex.set_backtrace locations + callsites
          else
            ex.set_backtrace Array(ex.backtrace) + callsites.map(&:to_s)
          end

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Check state first: future.wait!; raise future only if future.rejected?.
  2. Prefer future.value! — it returns the value when fulfilled and raises the rejection reason when rejected, no precondition needed.
  3. Use future.result ([:ok/:error, value, reason]) for explicit branching instead of raise.

Example fix

# before
raise future # Concurrent::Error: it is not rejected (future was fulfilled)

# after
future.wait!
raise future if future.rejected?
# or simply:
future.value! # returns value, or raises the rejection reason
Defensive patterns

Strategy: validation

Validate before calling

future.wait!
raise future.exception if future.rejected? # only raise after confirming rejection

Prevention

When it happens

Trigger: raise future where the future fulfilled successfully; future.exception called explicitly on a pending or fulfilled future; generic error-propagation code that re-raises whatever future it receives without checking rejected?.

Common situations: Writing `raise future` plumbing in rescue handlers; helpers that turn outcomes into exceptions without branching on state; testing rejection paths with futures that actually fulfilled.

Related errors


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