Skip to content

[#359] [#367] CleanupManager: keep the cleanup action counters consistent - #368

Open
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issue-359-cleanup-manager-accepted-count
Open

vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issue-359-cleanup-manager-accepted-count

Conversation

@vharseko

Copy link
Copy Markdown
Member

Fixes #359
Fixes #367

Problem

  • CleanupManager.offer() counts an action as accepted only after poll() can already perform it #359: CleanupManager.offer() put the action into the queue first and counted it as accepted afterwards. poll() drains the queue without the monitor that offer() holds, so in between it could perform the action and count it as performed. For that moment getPerformedCount() + getErrorCount() could exceed getAcceptedCount(), or equal it while an accepted action was still pending. The counter-based waits in the tests rely on that not happening.
  • CleanupManager loses count of accepted actions that poll() drops on a RuntimeException or clear() discards #367: two paths took accepted actions out of the queue without counting them, so the performed and error counts never caught up with the accepted count again:
    • poll() caught only PersistitException around each action. A RuntimeException left poll() (IOTaskRunnable.run() only logs it), was neither counted as an error nor posted to the AlertMonitor, and the rest of the drained work list (up to 2,000 actions) was lost.
    • clear() discarded the queued actions without counting them.

Fix

  • offer(): checks the remainingCapacity() of the queue, counts the action as accepted and only then adds it. Only offer() adds to the queue, and it holds the monitor while it does, so the checked capacity is still there when the action is added. Counting first and taking the count back on refusal would make the accepted count briefly too high and let it go down; with the capacity check both the accepted and the refused count stay exact and only ever grow.
  • poll(): a RuntimeException from an action is handled like a PersistitException: the action counts as an error, the alert is posted, and the rest of the work list is performed.
  • clear(): drains the queue into a list and counts the discarded actions; CleanupManagerMXBean reports them as the new getDiscardedCount(). drainTo() hands each action either to clear() or to a concurrent poll(), never to both.
  • New CLEANUP_OFFER_A/B/C sequencer locations and CLEANUP_OFFER_SCHEDULE for the test of the race; offer() passes CLEANUP_OFFER_A once the action is in the queue.

Tests

  • testActionAcceptedBeforePerformed: parks the test's offer() at CLEANUP_OFFER_A once the action is in the queue (a Condition limits the location to the test's thread), runs poll() on another thread and checks that the action already counts as accepted when it is performed.
  • testRuntimeExceptionCountedAsError: an action failing with a RuntimeException counts as an error, the action after it is performed, and getLastException() returns the exception.
  • testClearCountsDiscardedActions: after a poll() and a clear(), the accepted count equals the performed, error and discarded counts.
  • testOverflow: checks the exact accepted and refused counts instead of >= 0.
  • These four tests use a CleanupManager that is never started: no CLEANUP_MANAGER thread polls it and no Persistit thread offers to it, so only the test changes its counters.
  • Mutants killed, one test each:
    • accepted counted after the action is added: testActionAcceptedBeforePerformed, expected:<1> but was:<0>;
    • accepted counted before the capacity check: testOverflow, expected:<100000> but was:<200000>;
    • only PersistitException caught: testRuntimeExceptionCountedAsError fails with the IllegalStateException out of poll();
    • the old clear(): testClearCountsDiscardedActions, expected:<7> but was:<0>.
  • Full persistit/core suite on JDK 27 at a4b1baa: 603 tests, 0 failures, 0 errors, 5 skipped.

Note

waitForCleanupManager() in #360 waits until the performed and error counts add up to the accepted count. No test calls clear() before such a wait, so #360 needs no change for the new discarded count.

… keep the cleanup action counters consistent

offer() put the action into the queue first and counted it as accepted
afterwards (OpenIdentityPlatform#359). poll() drains the queue without the monitor that
offer() holds, so in between it could perform the action and count it as
performed: for that moment performed + errors could exceed the accepted
count. offer() now checks the remaining capacity of the queue, counts the
action as accepted and only then adds it. Only offer() adds to the queue,
and it holds the monitor while it does, so the checked capacity is still
there when the action is added. Both counters only ever grow.

Two paths took accepted actions out of the queue without counting them
(OpenIdentityPlatform#367), so the performed and error counts never caught up with the
accepted count again:
- poll() caught only PersistitException around each action. A
  RuntimeException left poll(), was neither counted nor posted to the
  AlertMonitor, and the rest of the drained work list was lost. It is now
  handled like a PersistitException.
- clear() discarded the queued actions. It now counts them, and
  CleanupManagerMXBean reports the count as getDiscardedCount().

Tests:
- testActionAcceptedBeforePerformed parks offer() at a new
  CLEANUP_OFFER_A sequence point once the action is in the queue, runs
  poll() on another thread and checks that the action already counts as
  accepted when it is performed.
- testRuntimeExceptionCountedAsError checks that an action failing with a
  RuntimeException counts as an error and the next action is performed.
- testClearCountsDiscardedActions checks that the accepted count equals
  the performed, error and discarded counts after clear().
- testOverflow and the tests above use a CleanupManager that is never
  started, so only the test changes its counters; testOverflow checks the
  exact accepted and refused counts.

Fixes OpenIdentityPlatform#359
Fixes OpenIdentityPlatform#367
@vharseko vharseko added bug java Pull requests that update java code concurrency Race conditions, locking and thread-safety labels Oct 17, 2026
@vharseko
vharseko requested a review from maximthomas October 17, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug concurrency Race conditions, locking and thread-safety java Pull requests that update java code

Projects

None yet

1 participant