ruby-concurrency/concurrent-ruby · error · ArgumentError
no block given
Error message
no block given
What it means
MVar is a synchronized single-slot container (empty/full states). #modify atomically takes the current value, yields it to your block, and puts the transformed value back — the block is essential, so calling modify (with or without a timeout) without one raises ArgumentError 'no block given' before any locking happens.
Source
Thrown at lib/concurrent-ruby/concurrent/mvar.rb:124
# If we timed out we won't be empty
if unlocked_empty?
@value = value
@full_condition.signal
apply_deref_options(value)
else
TIMEOUT
end
end
end
# Atomically `take`, yield the value to a block for transformation, and then
# `put` the transformed value. Returns the pre-transform value. A timeout can
# be set to limit the time spent blocked, in which case it returns `TIMEOUT`
# if the time is exceeded.
# @return [Object] the pre-transform value, or `TIMEOUT`
def modify(timeout = nil)
raise ArgumentError.new('no block given') unless block_given?
@mutex.synchronize do
wait_for_full(timeout)
# If we timed out we'll still be empty
if unlocked_full?
value = @value
@value = yield value
@full_condition.signal
apply_deref_options(value)
else
TIMEOUT
end
end
end
# Non-blocking version of `take`, that returns `EMPTY` instead of blocking.
def try_take!View on GitHub (pinned to 0b88d5ff75)
Solutions
- Always pass the transformation block: mvar.modify { |v| v + 1 }
- Remember the only positional argument is the timeout: mvar.modify(2) { |v| v + 1 }
- If the transformation is stored, pass it as a block: mvar.modify(&transform)
Example fix
# before
mvar.modify(2) # intended 'set to 2 after 2s'
# after
mvar.put(2) # plain overwrite (blocks when full)
mvar.modify(2) { |v| v * 10 } # 2 = timeout, block = transform Defensive patterns
Strategy: validation
Validate before calling
raise ArgumentError, 'transform block required' unless block_given?
mvar.modify(timeout) { |v| yield(v) } Type guard
->(obj) { obj.respond_to?(:call) } # then mvar.modify(&obj) Try / catch
begin
mvar.modify(timeout) { |v| transform.call(v) }
rescue ArgumentError => e
raise ArgumentError, 'modify needs a transform block' if /no block/.match?(e.message)
raise
end Prevention
- Remember modify(timeout = nil) { |v| ... } — the only positional arg is a timeout, not a value
- Use put(value) for plain writes; modify is for read-transform-write
- Check the MVar result for TIMEOUT after modify with a timeout instead of assuming success
When it happens
Trigger: mvar.modify with no block; mvar.modify(5) where 5 was intended as a new value rather than the timeout; helper methods that accept a timeout and a block but drop the & forwarding.
Common situations: Expecting modify(value) to behave like put(value); wrapping MVar in repository-style APIs where the transformation lives in a stored proc; first contact with MVar's Haskell-style API where every operation is block-based.
Related errors
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/c268f5318e08bb6b.
Report an issue: GitHub.