Skip to content

fix(atomic): initialize bucket atomics before publishing them in ETS - #202

Merged
epinault merged 2 commits into
masterfrom
fix/atomic-init-before-publish
Sep 2, 2026
Merged

fix(atomic): initialize bucket atomics before publishing them in ETS#202
epinault merged 2 commits into
masterfrom
fix/atomic-init-before-publish

Conversation

@epinault

@epinault epinault commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

The Atomic TokenBucket and LeakyBucket create an :atomics ref, insert it into ETS with insert_new, and only then write the initial packed value. Between those two steps a concurrent hit/5 can look the atomic up and read 0, which unpacks as an empty bucket at timestamp 0.

For the token bucket with refill_rate: 0 that hit is denied and lost. With a nonzero refill rate it works by accident, because (now - 0) * refill_rate is huge and gets capped at capacity. This is the cause of the intermittent failure in the Atomic TokenBucket "race condition" test (seen on #201, CI run 33683962078, job test (1.17, 27)).

The fix initializes both slots before insert_new, so a published atomic is always valid. If insert_new loses the race, the unused atomic is dropped and the retry reads the winner's.

Ships as part of 7.5.0: the release commit (#200) is on master but not yet tagged, so the changelog entry is filed under 7.5.0 rather than Unreleased.

Verification

  • Stress check: 64 concurrent hits on 2000 fresh keys with refill_rate: 0, asserting the final level is 0. Master lost hits on 2 keys; this branch on 0.
  • mix test 150 passed; test/hammer/atomic run 30 times, 30/30 pass.
  • mix compile --warnings-as-errors, mix credo --strict, mix format --check-formatted clean.

Test plan

  • CI

🤖 Generated with Claude Code by Emmanuel Pinault

https://claude.ai/code/session_011dH6kRtZqUkVpoGo79CKYG

epinault added 2 commits September 2, 2026 14:25
TokenBucket and LeakyBucket created the atomic, inserted it into ETS,
and only then wrote the initial packed value. Between insert_new and
the put, a concurrent hit could look the atomic up and read zero, which
unpacks as an empty bucket at timestamp 0. With refill_rate 0 that hit
is denied and lost; this is the intermittent failure in the Atomic
TokenBucket "race condition" test.

Initialize both slots before insert_new so a published atomic is
always valid. If insert_new loses, the unused atomic is simply dropped.

Stress check (64 concurrent hits on 2000 fresh keys, refill_rate 0):
master lost hits on 2 keys, this branch on 0.

Co-Authored-By: Emmanuel Pinault <dev@pinault-family.us>
Claude-Session: https://claude.ai/code/session_011dH6kRtZqUkVpoGo79CKYG
The 7.5.0 release commit is on master but not yet tagged, so this fix
ships with it.

Co-Authored-By: Emmanuel Pinault <dev@pinault-family.us>
Claude-Session: https://claude.ai/code/session_011dH6kRtZqUkVpoGo79CKYG
@epinault
epinault merged commit a964ae1 into master Sep 2, 2026
9 checks passed
@epinault
epinault deleted the fix/atomic-init-before-publish branch September 2, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant