Skip to content

[#304] Throw PersistitClosedException when a commit races Persistit.close()#305

Merged
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:persistit/fix-waitfordurability-close-304
Jul 24, 2026
Merged

[#304] Throw PersistitClosedException when a commit races Persistit.close()#305
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:persistit/fix-waitfordurability-close-304

Conversation

@vharseko

Copy link
Copy Markdown
Member

Fixes #304

Problem

A transaction commit racing Persistit.close() has two failure windows in JournalManager.waitForDurability:

  1. Raw IllegalStateException: close() nulls the _flusher reference before the committing thread reads it, so waitForDurability throws IllegalStateException("JOURNAL_FLUSHER is not running"). That is not a PersistitException, so callers' closed-state handling is bypassed — observed as the TransactionTest2.transactionsConcurrentWithPersistitClose flake on master run 30078532797, where the worker landed in the test's catch (Throwable) branch and the stranded-thread assert failed.
  2. Infinite poll (latent): a committing thread that captures the flusher reference just before it is cleared enters the JournalFlusher.waitForDurability polling loop; once the flusher stops, its durability marks never advance, and a flushedTimestamp allocated after the flusher's final cycle can never be covered — the loop kicks a stopped thread forever. Confirmed real: the new test hangs past a 10-second join deadline without the fix.

Fix

Throw PersistitClosedException in both windows:

  • JournalManager.waitForDurability throws PersistitClosedException instead of IllegalStateException when the flusher reference is already null, so a commit racing close() unwinds through the same documented path as every other closed-state operation.
  • The JournalFlusher.waitForDurability polling loop checks isStopped() after the success check. The flusher publishes its final _startTimestamp/_endTimestamp before stopping, so a single re-read after observing the stop is exact: either the final flush cycle covered the caller's timestamp (return success) or no future cycle ever will (throw PersistitClosedException).

TransactionTest2 needs no changes: PersistitClosedException is already one of its expected shutdown outcomes.

Testing

  • Two new JournalManagerTest cases, one per window: waitForDurabilityAfterCloseThrowsPersistitClosedException (direct call after close()) and waitForDurabilityOnStoppedFlusherThrowsInsteadOfSpinning (via crash(), which stops the flusher without clearing the reference). Both verified to fail without the fix — the first with the raw IllegalStateException, the second by hanging past its join deadline.
  • Full persistit/core suite on JDK 11: 592 tests, 0 failures, 0 errors, 5 skipped.

…it races Persistit.close()

A transaction commit racing Persistit.close() could reach
JournalManager.waitForDurability after close() cleared the
JOURNAL_FLUSHER reference and fail with a raw IllegalStateException,
bypassing callers' closed-state handling (seen as a
TransactionTest2.transactionsConcurrentWithPersistitClose flake). A
commit that instead captured the flusher reference just before it was
cleared would poll forever against the stopped thread.

Throw PersistitClosedException in both cases: when the flusher
reference is already null, and inside the JournalFlusher wait loop once
the flusher is stopped and its final durability marks cannot cover the
caller's timestamp. The final marks are published before the flusher
stops, so a single re-read after observing the stop distinguishes a
durable commit from one that can never be confirmed.

New JournalManagerTest cases cover both windows; without the fix the
first fails with the IllegalStateException and the second hangs past
its join deadline.

Fixes OpenIdentityPlatform#304
@vharseko vharseko added bug tests Test code changes labels Jul 24, 2026
@vharseko
vharseko requested a review from maximthomas July 24, 2026 09:28
@vharseko
vharseko merged commit 6e40198 into OpenIdentityPlatform:master Jul 24, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug tests Test code changes

Projects

None yet

2 participants