{"record":{"id":"4f1cd95386aa3b4b","repo":"ruby-concurrency/concurrent-ruby","slug":"an-executor-must-be-provided","errorCode":null,"errorMessage":"an executor must be provided","messagePattern":"an executor must be provided","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/dataflow.rb","lineNumber":57,"sourceCode":"  def dataflow_with(executor, *inputs, &block)\n    call_dataflow(:value, executor, *inputs, &block)\n  end\n  module_function :dataflow_with\n\n  def dataflow!(*inputs, &block)\n    dataflow_with!(Concurrent.global_io_executor, *inputs, &block)\n  end\n  module_function :dataflow!\n\n  def dataflow_with!(executor, *inputs, &block)\n    call_dataflow(:value!, executor, *inputs, &block)\n  end\n  module_function :dataflow_with!\n\n  private\n\n  def call_dataflow(method, executor, *inputs, &block)\n    raise ArgumentError.new('an executor must be provided') if executor.nil?\n    raise ArgumentError.new('no block given') unless block_given?\n    unless inputs.all? { |input| input.is_a? IVar }\n      raise ArgumentError.new(\"Not all dependencies are IVars.\\nDependencies: #{ inputs.inspect }\")\n    end\n\n    result = Future.new(executor: executor) do\n      values = inputs.map { |input| input.send(method) }\n      block.call(*values)\n    end\n\n    if inputs.empty?\n      result.execute\n    else\n      counter = DependencyCounter.new(inputs.size) { result.execute }\n\n      inputs.each do |input|\n        input.add_observer counter\n      end","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/dataflow.rb#L39-L75","documentation":"Concurrent.dataflow_with / dataflow_with! build a Future whose block runs once the input IVars resolve, scheduled on the executor you pass as the first argument. call_dataflow rejects a nil executor immediately, because scheduling on nothing would otherwise surface later as an obscure failure inside the Future machinery.","triggerScenarios":"`Concurrent.dataflow_with!(nil, f1, f2) { |a, b| a + b }`; an executor variable that is nil because configuration was missing or the assigning branch never ran; explicitly passing nil as a placeholder for the executor slot.","commonSituations":"Executor selection from config/YAML where the key is absent; custom thread pools created conditionally; refactoring between dataflow (which uses Concurrent.global_io_executor) and dataflow_with! and dropping the argument.","solutions":["Pass a real executor: `Concurrent.dataflow_with!(Concurrent.global_io_executor, f1, f2) { |a, b| a + b }` or your own ThreadPoolExecutor.","Default nil away: `Concurrent.dataflow_with!(executor || Concurrent.global_io_executor, *inputs, &job)`.","If no custom executor is needed, use plain `Concurrent.dataflow` / `Concurrent.dataflow!`, which run on the global IO executor.","Note the sibling checks enforced in the same method: a block is required and every input must be an IVar (e.g. a Future or another dataflow result)."],"exampleFix":"// before\nexecutor = config[:pool]        # nil when key missing\nConcurrent.dataflow_with!(executor, f1, f2) { |a, b| a + b }\n\n// after\nexecutor = config.fetch(:pool) { Concurrent.global_io_executor }\nConcurrent.dataflow_with!(executor, f1, f2) { |a, b| a + b }","handlingStrategy":"validation","validationCode":"executor = Concurrent.global_io_executor if executor.nil?\nConcurrent.dataflow_with!(executor, *inputs, &job)","typeGuard":"def executor_like?(e)\n  !e.nil? && e.respond_to?(:post)\nend","tryCatchPattern":"begin\n  Concurrent.dataflow_with!(executor, f1, f2, &job)\nrescue ArgumentError => e\n  raise unless e.message == 'an executor must be provided'\n  Concurrent.dataflow_with!(Concurrent.global_io_executor, f1, f2, &job)\nend","preventionTips":["dataflow_with/dataflow_with! take the executor as the FIRST argument — always supply it explicitly.","Default unresolved executor config to Concurrent.global_io_executor or a shared ThreadPoolExecutor.","Remember the sibling validations: a block is required and every input must be an IVar (Future or another dataflow result)."],"tags":["concurrent-ruby","dataflow","executor","argumenterror"],"backgroundTag":"missing-executor-argument","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}