Class NeoWhilst
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 accumulatedList<T>of body results when the truth-test goes false, or with the firstEthat any iteration's body raised. Routed throughNeoUtils.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.runtruth-test that exists to fan out additional concurrent bodies forWhilstLimit(limit > 1, ...)used to re-fire for sync-completing bodies (theAsyncFut.Whilst+CompletableFuture.completedFuturecase), dispatching one extra body call past short-circuit. v0.2.9 gates that block ons.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 finalList<T>preserves iteration order independent of body completion order.
VT-pinning audit (JDK 21 - 23)
NeoWhilst uses two layers of synchronized-based mutual exclusion:
synchronized (taskRunner.cbLock)inRunMap(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: theisFinishedcheck, theresults.set(slot, v)write, thec.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 insidem.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.synchronized (c)inRunMap(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.runfan-out block and in theCounterLimitreads guardsCounterLimit.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:
-
Asyncc.Whilst(org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>)Asyncc.DoWhilst(org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>)Asyncc.WhilstLimit(int, org.ores.async.NeoWhilstI.SyncTruthTest, org.ores.async.NeoWhilstI.AsyncTask<T, E>, org.ores.async.Asyncc.IAsyncCallback<java.util.List<T>, E>)
-
Constructor Summary
Constructors -
Method Summary
-
Constructor Details
-
NeoWhilst
public NeoWhilst()
-