ruby-concurrency/concurrent-ruby · error · Concurrent::ConcurrentUpdateError
Update failed
Error message
Update failed
What it means
`try_update!` on atomic references (AtomicReference and every class mixing in AtomicDirectUpdate) does one optimistic read-yield-compare_and_set cycle. When the CAS loses a race — another thread changed the value between your read and your write — it raises ConcurrentUpdateError ('Update failed'). This specific raise site is the `$VERBOSE` branch: when Ruby warnings are enabled (`ruby -w`, `$VERBOSE = true`), the error carries the full live backtrace so you can find the racing call sites.
Source
Thrown at lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb:29
true until compare_and_set(old_value = get, new_value = yield(old_value))
new_value
end
def try_update
old_value = get
new_value = yield old_value
return unless compare_and_set old_value, new_value
new_value
end
def try_update!
old_value = get
new_value = yield old_value
unless compare_and_set(old_value, new_value)
if $VERBOSE
raise ConcurrentUpdateError, "Update failed"
else
raise ConcurrentUpdateError, "Update failed", ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE
end
end
new_value
end
end
end
View on GitHub (pinned to 0b88d5ff75)
Solutions
- Switch to `ref.update { |v| ... }` — it retries the CAS internally until it succeeds and never raises for contention.
- If single-attempt semantics are required, rescue ConcurrentUpdateError and retry or recompute.
- Reduce contention: use AtomicFixnum#increment / AtomicBoolean operations, or shard the counter.
- Keep $VERBOSE on in development so this error's backtrace shows the true racing call sites.
Example fix
// before
ref.try_update! { |v| v + 1 } # raises ConcurrentUpdateError under contention
// after
ref.update { |v| v + 1 } # retries internally until the CAS succeeds Defensive patterns
Strategy: retry
Try / catch
begin
ref.try_update! { |v| compute(v) }
rescue Concurrent::ConcurrentUpdateError
retry
end Prevention
- Default to ref.update { ... } — it retries internally and never raises for contention.
- Use purpose-built atomics (AtomicFixnum#increment) for counters.
- Rescue ConcurrentUpdateError specifically, never bare Exception.
- Run development with $VERBOSE = true to get real racing call sites from this branch.
When it happens
Trigger: Two or more threads calling `ref.try_update! { |v| v + 1 }` on the same AtomicReference under contention; any shared counter/accumulator built on AtomicReference with single-shot update semantics; running the suite with `-W` or `$VERBOSE = true` selects this branch over the cached-backtrace one.
Common situations: Hot-path code where `update`'s retry loop was swapped for `try_update!` for micro-benchmarks; metrics counters on shared references; CI configurations that enable warnings and suddenly surface verbose backtraces for the same failure.
Related errors
- Required array size too large
- Could not initialize intrinsics
- Could not initialize intrinsics
- unbuffered channels cannot have a capacity
- capacity must be at least 1 for this buffer type
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/37d82bd706e5a224.
Report an issue: GitHub.