Class NeoWhilst

java.lang.Object
org.ores.async.NeoWhilst

public class NeoWhilst extends Object
Async-recursion engine behind Asyncc.Whilst(org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>) and Asyncc.DoWhilst(org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>) and their Limit variants.

Concurrency contract

  • At-most-once final callback. The final callback f.done(...) fires exactly once: either with the accumulated List<T> of body results when the truth-test goes false, or with the first E that any iteration's body raised. Routed through NeoUtils.fireFinalCallback(org.ores.async.ShortCircuit, java.lang.Object, org.ores.async.NeoEachI.IEachCallback<E>) which is idempotent under concurrent settle attempts.
  • No body invocations past short-circuit. Once a body has failed or the truth-test has gone false, no further bodies run. This invariant was tightened in v0.2.9 (see RunMap(org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, java.util.List<T>, org.ores.async.CounterLimit, org.ores.async.ShortCircuit, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>)): the post-m.run truth-test that exists to fan out additional concurrent bodies for WhilstLimit(limit > 1, ...) used to re-fire for sync-completing bodies (the AsyncFut.Whilst + CompletableFuture.completedFuture case), dispatching one extra body call past short-circuit. v0.2.9 gates that block on s.isShortCircuited() || taskRunner.isFinished().
  • FIFO results ordering. Each iteration writes its body's return value into a pre-allocated slot at index c.getStartedCount()-before-increment, so the final List<T> preserves iteration order independent of body completion order.

VT-pinning audit (JDK 21 - 23)

NeoWhilst uses two layers of synchronized-based mutual exclusion:

  1. synchronized (taskRunner.cbLock) in RunMap(org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, java.util.List<T>, org.ores.async.CounterLimit, org.ores.async.ShortCircuit, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>) guards the per-iteration callback's settle path: the isFinished check, the results.set(slot, v) write, the c.incrementFinished() counter, and the short-circuit check. The critical section is microseconds — one branch, one slot store, one atomic increment, one flag read — and runs at most once per iteration. Pinning impact is bounded by the body's own duration: a synchronous body enters this section synchronously inside m.run; an async body enters it from whichever thread settles the future. In either case the lock is held only across the four operations above, never across a user callback.
  2. synchronized (c) in RunMap(org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, java.util.List<T>, org.ores.async.CounterLimit, org.ores.async.ShortCircuit, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>)'s post-m.run fan-out block and in the CounterLimit reads guards CounterLimit.isBelowCapacity(). Critical section is a single integer comparison. Held briefly. Never held across user code.

On JDK 21 - 23, synchronized blocks pin the virtual thread's carrier thread for the duration of the monitor hold. Because both critical sections above are microsecond-scale and never wrap user code (the user's body runs on the same thread but outside the monitor; the user's final callback runs from fireFinalCallback, also outside), the pinning footprint is comparable to or smaller than NeoLock's. Audited at v0.2.9 against AsyncFutExtendedTest (20 tests including the strict short-circuit assertion post-race-fix) plus the WhilstTest suite on JDK 17 and 21 — no observable latency floor and no thread starvation.

On JDK 24+ ([JEP 491] - Synchronize Virtual Threads without Pinning) pinning is removed entirely and these become standard non-pinning monitors. No code change required to benefit from that.

A future major release may migrate the per-iteration cbLock to a ReentrantLock and the CounterLimit state to AtomicInteger pairs with happens-before reads — giving JDK 21-23 the same non-pinning behavior as JDK 24+. Not yet warranted: the pin windows are short enough that the engineering cost of removing them outweighs the theoretical gain. See NeoLock's v0.2.5 audit for the same conclusion under more aggressive contention (1k acquirers).

Pre-v0.2.9 race history

v0.2.8 and earlier had a sync-body race in RunMap(org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, java.util.List<T>, org.ores.async.CounterLimit, org.ores.async.ShortCircuit, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>) where the truth-test fired in two places: inside the per-task done callback (which already recursed if the loop should continue) AND in a post-m.run block intended for async-body fan-out at limit > 1. For sync-completing bodies the post-m.run test would race the chain's settlement and dispatch one extra body call past short-circuit. Surfaced by AsyncFutExtendedTest#whilst_short_circuits_on_body_failure which had to be relaxed to counter == 4 || 5 in v0.2.8-rc3. v0.2.9 closes the race by gating the second block on taskRunner.isFinished(); the test now strictly asserts 4.

See Also:
  • Constructor Details

    • NeoWhilst

      public NeoWhilst()