#3868·mockito

after() busy-spins for its whole duration once verification succeeds

Author: ARV0007Created Sep 27, 2026Updated Sep 27, 2026

What happens

after(millis) is documented to wait the full period and then report the result. Internally, VerificationOverTimeImpl.verify() polls the delegate mode in a loop until the timer runs out:

java
while (timer.isCounting()) {
    try {
        delegate.verify(data);
        if (returnOnSuccess) {
            return;
        } else {
            error = null;
        }
    } catch (AssertionError e) {
        error = handleVerifyException(e);   // sleeps pollingPeriodMillis
    }
}

The only sleep() is inside handleVerifyException, i.e. on the failure path. For after(), returnOnSuccess is false, so once the delegate is satisfied the loop re-runs the full verification back to back with no pause until the duration elapses.

After configures a 10 ms polling period, so after(200) is meant to poll about 20 times. In practice it polls millions of times and keeps one core at 100% for the whole window.

Evidence

Before #3859, every poll re-captured each matching invocation once, so the number of captured values is a direct count of how many times the loop ran:

  • The standalone reproduction of the loop in #3859 captured ~13.8 million values for after(200).times(3) with three invocations — about 4.6 million polls in 200 ms.
  • @raphw reproduced the over-capture against real Mockito while reviewing #3859 (~27k values with the fix reverted). Real Mockito does much more work per poll, so the count is lower, but it is still far above the ~20 polls a 10 ms period implies for a 200 ms window.

#3859 stops the memory growth by capturing each invocation once, but deliberately leaves the loop alone, so the CPU cost is unchanged: every after() in a test suite pins a core for its whole duration, and each pass re-scans every invocation on the mock.

Expected

Once the delegate is satisfied, after() should wait pollingPeriodMillis before polling again, the same as it does after a failed poll. The result for after() can only change when new invocations arrive, so polling every 10 ms loses nothing.

Possible fix

Sleep on the success path when returnOnSuccess is false, e.g.

java
if (returnOnSuccess) {
    return;
}
error = null;
sleep(pollingPeriodMillis);

timeout() is unaffected (it returns on the first success). The early-failure behaviour for atMost() / never() is unaffected (that path throws immediately in handleVerifyException).

This touches the same method as #3859, so I'd suggest landing it after that PR. Happy to send the PR once #3859 is settled, with a test that bounds the number of delegate polls during an after() window.