If IndexWriter's InfoStream throws while a full in-memory buffer is handed over for flushing, that buffer stays registered as flushing forever, so the writer hangs in its own self-close after an Error, or in close() and rollback() after an exception (every release since 6.2.0)

Finding LFV-846Area: index / IndexWriter flush hand-off (DocumentsWriterFlushControl.checkOutForFlush, updateStallState, waitForFlush)Priority: lowImpact: hangNovelty: new

Finding page for LFV-846

AI-assisted finding, confirmed by automated checks, not yet triaged by a human Lucene committer. This site files nothing upstream.

What it shows

You index with IndexWriter's InfoStream turned on, and one logging call throws (an OutOfMemoryError while Lucene builds its 'now stalling flushes' message, or an exception from the logging backend) just as a full in-memory buffer is handed over to be flushed. You expect what the IndexWriter javadoc promises: after an Error the writer closes itself, and after an exception close() or rollback() still finish. Instead the buffer is already registered as 'flushing' when the log call throws, nothing ever flushes or releases it, and every later wait for running flushes waits forever: after an Error addDocument hangs inside the writer's own rollback, and after an exception close() and rollback() hang (and, by code reading, commit() and an NRT reopen too).

The test shows that an OutOfMemoryError thrown by the InfoStream while IndexWriter logs 'now stalling flushes' during the hand-off of a full buffer (DocumentsWriterFlushControl.checkOutForFlush) leaves addDocument blocked forever in the writer's own tragic rollback (waitForFlush).

Expected vs actual

Expected addDocument to rethrow the Error and the writer to close itself (getTragicException() set, isOpen() false); actual: addDocument is still blocked after 10 s (addDocument should rethrow the Error, but is still blocked, plus the runner's ThreadLeakError showing the thread in waitForFlush), with -ea and -da.

Files

Run it

# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble   (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar   # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar   # JUnit Platform console launcher (Maven Central)

javac -d classes -cp "$CP" $(find src -name '*.java')

# run 1: assertions on, seed DEADBEEF
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=DEADBEEF \
     -cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
     execute --select-class org.apache.lucene.index.TestErrorInStallMessageClosesWriter

Gradle alternative: copy the test into the matching module's src/test/ tree of the checkout and run ./gradlew -p lucene/<module> test --tests <class> -Ptests.seed=<seed> (add -Ptests.asserts=false for a run without assertions).

Feedback

Open an issue in lucene-ai-bug-hunt. Please do not open issues or pull requests in apache/lucene about this finding unless a Lucene committer agrees it should be filed.

Snapshot of 2026-11-03. AI-assisted; not yet triaged by a human Lucene committer. Feedback: open an issue in lucene-ai-bug-hunt.