ruby-concurrency/concurrent-ruby · error · ArgumentError
cannot provide both an observer and a block
Error message
cannot provide both an observer and a block
What it means
IVar#add_observer lets you register a callback for completion: either an observer object plus method symbol, or a bare block. Supplying both an observer object and a block is ambiguous — the library cannot tell which callback to invoke — so it raises ArgumentError 'cannot provide both an observer and a block'.
Source
Thrown at lib/concurrent-ruby/concurrent/ivar.rb:82
raise ArgumentError.new('provide only a value or a block')
end
super(&nil)
synchronize { ns_initialize(value, opts, &block) }
end
# Add an observer on this object that will receive notification on update.
#
# Upon completion the `IVar` will notify all observers in a thread-safe way.
# The `func` method of the observer will be called with three arguments: the
# `Time` at which the `Future` completed the asynchronous operation, the
# final `value` (or `nil` on rejection), and the final `reason` (or `nil` on
# fulfillment).
#
# @param [Object] observer the object that will be notified of changes
# @param [Symbol] func symbol naming the method to call when this
# `Observable` has changes`
def add_observer(observer = nil, func = :update, &block)
raise ArgumentError.new('cannot provide both an observer and a block') if observer && block
direct_notification = false
if block
observer = block
func = :call
end
synchronize do
if event.set?
direct_notification = true
else
observers.add_observer(observer, func)
end
end
observer.send(func, Time.now, self.value, reason) if direct_notification
observer
endView on GitHub (pinned to 0b88d5ff75)
Solutions
- Use the observer-object form: ivar.add_observer(listener, :update)
- Or use the block form only: ivar.add_observer { |time, value, reason| handle(value) }
- In adapter code, pick one shape at the boundary and strip the other before calling add_observer
Example fix
# before
ivar.add_observer(listener, :update) { |v| puts v }
# after
ivar.add_observer(listener, :update)
# or
ivar.add_observer { |time, value, reason| puts value } Defensive patterns
Strategy: validation
Validate before calling
def subscribe(ivar, observer = nil, func = :update, &block) raise ArgumentError, 'observer or block, not both' if observer && block block ? ivar.add_observer(&block) : ivar.add_observer(observer, func) end
Try / catch
begin ivar.add_observer(observer, :update) rescue ArgumentError => e raise if /observer and a block/.match?(e.message) === false # strip one of the two callback shapes and retry once ivar.add_observer(observer, :update) end
Prevention
- Choose one callback style (object or block) for observer registration across the project
- In adapter layers, normalize user callbacks to a single shape before touching the IVar
- Cover observer registration in unit tests with both styles, separately
When it happens
Trigger: ivar.add_observer(listener, :update) { |v| ... } — object plus block; pasting observer-object sample code and adding a block convenience on top; adapters that always pass an observer and also forward any block.
Common situations: Migrating from observer-object style to block style (or vice versa) and leaving both call shapes in place; observer frameworks that wrap user callbacks in objects while the caller also supplies a block.
Related errors
- provide only a value or a block
- must set with either a value or a block
- `min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}
- `min_threads` cannot be more than `max_threads`
- no block given
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/d83d7bab61150c38.
Report an issue: GitHub.