Summary

Reduces the frequency of LockSupport.unpark calls in ZScheduler, which is a hot-path JVM syscall identified as a bottleneck in #9878.

Problem

maybeUnparkWorker is called on every task submission and every time a worker transitions from searching → found work. Inside, it unconditionally calls idle.poll() (a ConcurrentLinkedQueue CAS) and, when successful, LockSupport.unpark — both expensive operations.

Changes

1. maybeUnparkWorker — three fast-path early returns

private def maybeUnparkWorker(currentState: Int): Unit = {
val currentSearching = currentState & 0xffff
if (currentSearching > 0) return // someone is already searching
val currentActive = (currentState & 0xffff0000) >> 16
if (currentActive == poolSize) return // all workers busy
if (idle.isEmpty) return // nothing to unpark
val worker = idle.poll()
if (worker ne null) {
state.getAndAdd(0x10001)
worker.active = true
LockSupport.unpark(worker)
}
}
  • currentSearching > 0: If at least one worker is already scanning for tasks, waking another is wasteful — the searching worker will find and then call maybeUnparkWorker itself if more are needed. This is the most impactful guard on a loaded system.
  • idle.isEmpty: Avoids the CAS + node allocation of poll() when the queue is already empty.

2. submit — conditional notification on local-queue success

Previously, every submit called maybeUnparkWorker unconditionally. Now:

  • External submit (null/blocking worker → global queue): always notify.
  • Local queue overflow → global queue: always notify.
  • Local queue success: only notify when currentActive < poolSize. When the submitting thread is itself a ZScheduler.Worker, it will process its local queue in the next iteration; waking an idle thread is only useful if there is spare capacity.

Tradeoffs

  • Fairness vs throughput: The currentSearching > 0 guard can delay waking idle threads under pathological scheduling. In practice, the searching worker resolves within microseconds and either finds work (cascade-notifying as needed) or parks again.
  • idle.isEmpty is non-linearizable: ConcurrentLinkedQueue.isEmpty is O(1) but can return a stale false. The subsequent poll() null-check handles this safely.

Testing

This is a performance-only change with no behavioral differences under correct execution. The existing test suite covers correctness.

Fixes #9878

/claim #9878

Claim

Total prize pool $850
Total paid $0
Status Pending
Submitted March 01, 2026
Last updated March 01, 2026

Contributors

HH

hhhcccbbb

@hhhcccbbb

100%

Sponsors

ZI

ZIO

@ZIO

$850