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

  1. Switch to `ref.update { |v| ... }` — it retries the CAS internally until it succeeds and never raises for contention.
  2. If single-attempt semantics are required, rescue ConcurrentUpdateError and retry or recompute.
  3. Reduce contention: use AtomicFixnum#increment / AtomicBoolean operations, or shard the counter.
  4. 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

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


AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21). Data as JSON: /api/errors/37d82bd706e5a224. Report an issue: GitHub.