ruby-concurrency/concurrent-ruby · error · ArgumentError

Not all dependencies are IVars. Dependencies: #{ inputs.insp

Error message

Not all dependencies are IVars.
Dependencies: #{ inputs.inspect }

What it means

Every input to the Concurrent.dataflow* family must be an IVar (Future, Promise, IVar, or a prior dataflow result) because dataflow works by observing completion of those objects. call_dataflow checks inputs.all? { |i| i.is_a?(Concurrent::IVar) } and raises ArgumentError including the inspected dependency list when any input fails. Raw Ruby values are rejected because there is nothing to wait on.

Source

Thrown at lib/concurrent-ruby/concurrent/dataflow.rb:60

  module_function :dataflow_with

  def dataflow!(*inputs, &block)
    dataflow_with!(Concurrent.global_io_executor, *inputs, &block)
  end
  module_function :dataflow!

  def dataflow_with!(executor, *inputs, &block)
    call_dataflow(:value!, executor, *inputs, &block)
  end
  module_function :dataflow_with!

  private

  def call_dataflow(method, executor, *inputs, &block)
    raise ArgumentError.new('an executor must be provided') if executor.nil?
    raise ArgumentError.new('no block given') unless block_given?
    unless inputs.all? { |input| input.is_a? IVar }
      raise ArgumentError.new("Not all dependencies are IVars.\nDependencies: #{ inputs.inspect }")
    end

    result = Future.new(executor: executor) do
      values = inputs.map { |input| input.send(method) }
      block.call(*values)
    end

    if inputs.empty?
      result.execute
    else
      counter = DependencyCounter.new(inputs.size) { result.execute }

      inputs.each do |input|
        input.add_observer counter
      end
    end

    result

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Wrap every non-IVar input: Concurrent.dataflow(Concurrent.future { 2 }, future_a) { |a, b| a + b }
  2. Splat arrays of dependencies: Concurrent.dataflow(*futures) { |*vs| ... }
  3. Pre-check inputs.all? { |i| i.is_a?(Concurrent::IVar) } and raise early with your own message
  4. Guard against nil dependencies before building the graph

Example fix

# before
Concurrent.dataflow(2, future_a) { |a, b| a + b }
# after
Concurrent.dataflow(Concurrent.future { 2 }, future_a) { |a, b| a + b }
Defensive patterns

Strategy: type-guard

Validate before calling

inputs = inputs.map { |i| i.is_a?(Concurrent::IVar) ? i : Concurrent.future { i } }
Concurrent.dataflow(*inputs) { |a, b| a + b }

Type guard

def ivar_input?(input)
  input.is_a?(Concurrent::IVar) # true for Future, Promise, IVar, dataflow results
end

Try / catch

begin
  Concurrent.dataflow(*inputs, &task)
rescue ArgumentError => e
  raise unless e.message.start_with?('Not all dependencies are IVars')
  inputs = inputs.map { |i| i.is_a?(Concurrent::IVar) ? i : Concurrent.future { i } }
  retry
end

Prevention

When it happens

Trigger: Concurrent.dataflow(2, future_a) { |a, b| a + b } mixing a raw Integer with a Future; passing the Array itself instead of splatting (dataflow(futures) vs dataflow(*futures)); a dependency variable that evaluated to nil; feeding Threads, plain procs, or monads from other libraries as dependencies.

Common situations: Seeding a dataflow graph with constant inputs without wrapping them in Concurrent.future; refactoring Futures into plain values and forgetting the wrapper; splat bugs where the whole array arrives as one argument; a lookup method returning nil for a missing dependency.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


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