{"record":{"id":"a4cdf0f4af7da933","repo":"ruby-concurrency/concurrent-ruby","slug":"no-block-given-a4cdf0","errorCode":null,"errorMessage":"no block given","messagePattern":"no block given","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb","lineNumber":25,"sourceCode":"module Concurrent\n\n  # An executor service in which every operation spawns a new,\n  # independently operating thread.\n  #\n  # This is perhaps the most inefficient executor service in this\n  # library. It exists mainly for testing an debugging. Thread creation\n  # and management is expensive in Ruby and this executor performs no\n  # resource pooling. This can be very beneficial during testing and\n  # debugging because it decouples the using code from the underlying\n  # executor implementation. In production this executor will likely\n  # lead to suboptimal performance.\n  #\n  # @note Intended for use primarily in testing and debugging.\n  class SimpleExecutorService < RubyExecutorService\n\n    # @!macro executor_service_method_post\n    def self.post(*args)\n      raise ArgumentError.new('no block given') unless block_given?\n      Thread.new(*args) do\n        Thread.current.abort_on_exception = false\n        yield(*args)\n      end\n      true\n    end\n\n    # @!macro executor_service_method_left_shift\n    def self.<<(task)\n      post(&task)\n      self\n    end\n\n    # @!macro executor_service_method_post\n    def post(*args, &task)\n      raise ArgumentError.new('no block given') unless block_given?\n      return false unless running?\n      @count.increment","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb#L7-L43","documentation":"SimpleExecutorService is a no-pooling executor intended for testing and debugging; it spawns one new thread per task. Its class-level .post requires the task as a block and raises ArgumentError 'no block given' when called without one, before any thread is created.","triggerScenarios":"Concurrent::SimpleExecutorService.post(callable) passing a Proc positionally; calling .post with only args (e.g. .post(1, 2)) intending them for the task; copy-pasting instance-style code onto the class interface.","commonSituations":"Quick test doubles replacing a real thread pool; scripts that store work in lambdas and forget the & when posting; learning the difference between the class-level convenience API and the instance API.","solutions":["Pass a block: Concurrent::SimpleExecutorService.post { do_work }","If the task is stored in a variable, convert it: .post(&callable)","Prefer the instance API (.new.post) when you need lifecycle control, and pass the block the same way"],"exampleFix":"# before\nwork = -> { crunch_numbers }\nConcurrent::SimpleExecutorService.post(work)\n\n# after\nwork = -> { crunch_numbers }\nConcurrent::SimpleExecutorService.post(&work)","handlingStrategy":"validation","validationCode":"raise ArgumentError, 'task block required' unless block_given?\nConcurrent::SimpleExecutorService.post { yield }","typeGuard":"->(obj) { obj.respond_to?(:call) } # then .post(&obj)","tryCatchPattern":"begin\n  Concurrent::SimpleExecutorService.post(&task)\nrescue ArgumentError\n  raise if block_given? # real bug\n  logger.error('post called without a task block')\nend","preventionTips":["Treat SimpleExecutorService as test-only scaffolding; in production prefer global_io_executor/global_fast_executor","Route all posts through helper methods that assert a block exists","Convert stored lambdas with & consistently across the codebase"],"tags":["ruby","concurrency","executor","block-argument","argument-validation","testing"],"backgroundTag":"missing-block-argument","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}