{"record":{"id":"bfb4e350faf4b232","repo":"ruby-concurrency/concurrent-ruby","slug":"recursive-call-to-value-during-evaluation-of-the","errorCode":null,"errorMessage":"Recursive call to #value during evaluation of the Delay","messagePattern":"Recursive call to #value during evaluation of the Delay","errorType":"exception","errorClass":"Concurrent::IllegalOperationError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/delay.rb","lineNumber":92,"sourceCode":"    # @return [Object] the current value of the object\n    #\n    # @!macro delay_note_regarding_blocking\n    def value(timeout = nil)\n      if @executor # TODO (pitr 12-Sep-2015): broken unsafe read?\n        super\n      else\n        # this function has been optimized for performance and\n        # should not be modified without running new benchmarks\n        synchronize do\n          execute = @evaluation_started = true unless @evaluation_started\n          if execute\n            begin\n              set_state(true, @task.call, nil)\n            rescue => ex\n              set_state(false, nil, ex)\n            end\n          elsif incomplete?\n            raise IllegalOperationError, 'Recursive call to #value during evaluation of the Delay'\n          end\n        end\n        if @do_nothing_on_deref\n          @value\n        else\n          apply_deref_options(@value)\n        end\n      end\n    end\n\n    # Return the value this object represents after applying the options\n    # specified by the `#set_deref_options` method. If the delayed operation\n    # raised an exception, this method will raise that exception (even when)\n    # the operation has already been executed).\n    #\n    # @param [Numeric] timeout the maximum number of seconds to wait\n    # @return [Object] the current value of the object\n    # @raise [Exception] when `#rejected?` raises `#reason`","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/delay.rb#L74-L110","documentation":"Concurrent::Delay runs its block exactly once, on the first call to #value (or #deref/#wait). Under the object's re-entrant monitor the evaluating thread sets @evaluation_started (delay.rb:84); a second call to #value made from inside the Delay's own block on the same thread reaches the elsif at delay.rb:91 while the state is still incomplete, and the library raises Concurrent::IllegalOperationError instead of deadlocking the monitor or returning a half-computed value.","triggerScenarios":"The Delay's block dereferences the same Delay: d = Concurrent::Delay.new { d.value }; a memoization cache where computing key A re-enters cache[A].value; two lazily-initialized resources whose blocks dereference each other; a callback invoked synchronously inside the block calls delay.value again.","commonSituations":"Converting recursive algorithms (tree walks, fibonacci, dependency resolution) to lazy memoized form with Delay; building dependency graphs that accidentally contain a cycle; refactors that pass the Delay instance into its own block; self-referential singleton initialization.","solutions":["Remove the self-reference: compute the recursive step with plain arguments/methods and let the Delay block only orchestrate the top level.","Replace recursion with iteration inside the block (accumulate into a local variable).","Model multi-step dependencies with Concurrent::Promises future chains (then/flat), which compose acyclic dependencies cleanly.","If re-entry is possible but unexpected, rescue Concurrent::IllegalOperationError at the call site and surface a domain-specific cycle error."],"exampleFix":"# before\nfact = Concurrent::Delay.new { fact.value * 1 } # re-enters itself on first #value\n\n# after\ndef compute(n) = n.zero? ? 1 : n * compute(n - 1)\nfact = Concurrent::Delay.new { compute(5) }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  delay.value\nrescue Concurrent::IllegalOperationError => e\n  raise DependencyCycleError, \"Delay block re-enters the Delay: #{e.message}\"\nend","preventionTips":["Never reference the Delay object inside its own block; pass inputs as arguments.","Keep recursive algorithms out of Delay blocks; make them iterative or plain methods.","Audit dependency graphs for cycles before wiring Delays together.","Prefer Concurrent::Promises chains (then/flat) for multi-step lazy dependencies."],"tags":["concurrency","ruby","lazy-initialization","recursion","deadlock"],"backgroundTag":"recursive-lazy-initialization","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}