Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@hearnadam
Copy link
Collaborator

@hearnadam hearnadam commented Dec 26, 2025

/fixes #9093 /claim #9093
Rewrite Semaphore to use a single AtomicLong instead of a Ref[Either[Queue, Long]].

The old implementation used an immutable Scala queue inside a Ref to track waiters. Every acquire/release went through ref.modify, which meant allocating new queue instances and ZIO effects even on the happy path.

The new implementation packs permit/waiter state into a single AtomicLong

  • positive = available permits
  • negative = waiters are present

The new implementation uses a MutableConcurrentQueue for pending waiters instead of copying immutable queues, though this could be changed for a more performant queue later.

The new Waiter trait is used to represent waiters requiring a single permit and multiple permits. This prevents allocating an AtomicLong per waiter in the most common case.

Benchmarks show a 30-100% performance improvement. See benchmarks here: https://jmh.morethan.io/?gists=5889786ef8c51621de513da9ec45091d,1b73e3a975d46b09f92936c30e3b83cb

Rewrite Semaphore to use a single AtomicLong instead of a Ref[Either[Queue, Long]].

The old implementation used an immutable Scala queue inside a Ref to track waiters. Every acquire/release went through ref.modify, which meant allocating new queue instances and ZIO effects even on the happy path.

The new implementation packs permit/waiter state into a single AtomicLong
- positive = available permits
- negative = waiters are present

The new implementation uses a MutableConcurrentQueue for pending waiters instead of copying immutable queues, though this could be changed for a more performant queue later.

The new `Waiter` trait is used to represent waiters requiring a single permit and multiple permits. This prevents allocating an AtomicLong per waiter in the most common case.

Benchmarks show a 30-100% performance improvement. See benchmarks here: https://jmh.morethan.io/?gists=5889786ef8c51621de513da9ec45091d,1b73e3a975d46b09f92936c30e3b83cb

if (waiterPermits <= remaining) {
// We have enough permits to fulfill this waiter completely
fulfillWaiter(state, waiterPermits)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can have a race condition if multi releaseUnsafe get triggerred, and only some of them can theoratically fullfill. In this case the left over will basically running into assertions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ZIO's Semaphore performance not too great

2 participants