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
resultView on GitHub (pinned to 0b88d5ff75)
Solutions
- Wrap every non-IVar input: Concurrent.dataflow(Concurrent.future { 2 }, future_a) { |a, b| a + b }
- Splat arrays of dependencies: Concurrent.dataflow(*futures) { |*vs| ... }
- Pre-check inputs.all? { |i| i.is_a?(Concurrent::IVar) } and raise early with your own message
- 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
- Splat dependency arrays: dataflow(*futures), never dataflow(futures)
- Wrap constants as Concurrent.future { value } before adding them as inputs
- Assert inputs.all? { |i| i.is_a?(Concurrent::IVar) } in specs covering graph construction
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.