apache / apache/lucene

IndexWriter race condition [LUCENE-7499]

Open
#8,550 0 comments 0 reactions 0 assignees View on GitHub
affects-version:5.5.2 affects-version:6.2 legacy-jira-priority:Minor module:core/index type:bug
Dominant language
Java
Stars
3.6k
Forks
1.4k
Avg merge
2d 11h
Merged PRs (30d)
88

Description

separated out from #3659

I've seen race condition diagnostics for Lucene code that could be related to this. Line numbers are for Lucene 5.5.2, but the problem seems present in master too.

AFAICT: StandardDirectoryReader has an IndexWriter, and it checks things are up-to-date by checking if there are pending in-memory updates by looking at IndexWriter.SegmentInfos.version. That read is not synchronized relative to its write from IndexWriter.updateDocument

I'm not enough of a lucene expert to suggest how to fix this in detail. If I would guess at how Lucene roughly works, I would suggest the following

- when applying pending in-memory changes, this takes a lock, and under lock there is a "changed = true" assignment.
- when checking for changes, take lock, return changed.
- when writing pending in-memory changes, take lock, write out file, set "changed=false", release lock.

The problem can be reproduced by running some of the Gerrit acceptance tests under Threadsanitizer. I don't know of open-source tooling to set that up, unfortunately.

WARNING: ThreadSanitizer: data race (pid=21636)
Write of size 8 at 0x7f24ad9d3210 by thread T36 (mutexes: write M431922409782456832):
`#0` org.apache.lucene.index.SegmentInfos.changed()V (SegmentInfos.java:944)
`#1` org.apache.lucene.index.IndexWriter.newSegmentName()Ljava/lang/String; (IndexWriter.java:1652)
`#2` org.apache.lucene.index.DocumentsWriter.ensureInitialized(Lorg/apache/lucene/index/DocumentsWriterPerThreadPool$ThreadState;)V (DocumentsWriter.java:391)
`#3` org.apache.lucene.index.DocumentsWriter.updateDocument(Ljava/lang/Iterable;Lorg/apache/lucene/analysis/Analyzer;Lorg/apache/lucene/index/Term;)Z (DocumentsWriter.java:445)
`#4` org.apache.lucene.index.IndexWriter.updateDocument(Lorg/apache/lucene/index/Term;Ljava/lang/Iterable;)V (IndexWriter.java:1477)
`#5` com.google.gerrit.lucene.AutoCommitWriter.updateDocument(Lorg/apache/lucene/index/Term;Ljava/lang/Iterable;)V (AutoCommitWriter.java:100)
`#6` org.apache.lucene.index.TrackingIndexWriter.updateDocument(Lorg/apache/lucene/index/Term;Ljava/lang/Iterable;)J (TrackingIndexWriter.java:55)
`#7` com.google.gerrit.lucene.AbstractLuceneIndex$4.call()Ljava/lang/Long; (AbstractLuceneIndex.java:250)
`#8` com.google.gerrit.lucene.AbstractLuceneIndex$4.call()Ljava/lang/Object; (AbstractLuceneIndex.java:247)
`#9` com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly()V (TrustedListenableFutureTask.java:108)
`#10` com.google.common.util.concurrent.InterruptibleTask.run()V (InterruptibleTask.java:41)
`#11` com.google.common.util.concurrent.TrustedListenableFutureTask.run()V (TrustedListenableFutureTask.java:77)
`#12` java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V (ThreadPoolExecutor.java:1142)
`#13` java.util.concurrent.ThreadPoolExecutor$Worker.run()V (ThreadPoolExecutor.java:617)
`#14` java.lang.Thread.run()V (Thread.java:745)
`#15` (Generated Stub)

Previous read of size 8 at 0x7f24ad9d3210 by thread T29 (mutexes: write M1060737507754061632):
`#0` org.apache.lucene.index.IndexWriter.nrtIsCurrent(Lorg/apache/lucene/index/SegmentInfos;)Z (IndexWriter.java:4592)
`#1` org.apache.lucene.index.StandardDirectoryReader.doOpenFromWriter(Lorg/apache/lucene/index/IndexCommit;)Lorg/apache/lucene/index/DirectoryReader; (StandardDirectoryReader.java:282)
`#2` org.apache.lucene.index.StandardDirectoryReader.doOpenIfChanged(Lorg/apache/lucene/index/IndexCommit;)Lorg/apache/lucene/index/DirectoryReader; (StandardDirectoryReader.java:261)
`#3` org.apache.lucene.index.StandardDirectoryReader.doOpenIfChanged()Lorg/apache/lucene/index/DirectoryReader; (StandardDirectoryReader.java:251)
`#4` org.apache.lucene.index.DirectoryReader.openIfChanged(Lorg/apache/lucene/index/DirectoryReader;)Lorg/apache/lucene/index/DirectoryReader; (DirectoryReader.java:137)
`#5` com.google.gerrit.lucene.WrappableSearcherManager.refreshIfNeeded(Lorg/apache/lucene/search/IndexSearcher;)Lorg/apache/lucene/search/IndexSearcher; (WrappableSearcherManager.java:148)
`#6` com.google.gerrit.lucene.WrappableSearcherManager.refreshIfNeeded(Ljava/lang/Object;)Ljava/lang/Object; (WrappableSearcherManager.java:68)
`#7` org.apache.lucene.search.ReferenceManager.doMaybeRefresh()V (ReferenceManager.java:176)
`#8` org.apache.lucene.search.ReferenceManager.maybeRefreshBlocking()V (ReferenceManager.java:253)
`#9` org.apache.lucene.search.ControlledRealTimeReopenThread.run()V (ControlledRealTimeReopenThread.java:245)
`#10` (Generated Stub)

---
Migrated from [LUCENE-7499](https://issues.apache.org/jira/browse/LUCENE-7499) by Han-Wen NIenhuys

Contributor guide

Open the contributing guide

Research direction

Start by reading IndexWriter.nrtIsCurrent, SegmentInfos.changed, and StandardDirectoryReader.doOpenFromWriter at the Lucene 5.5.2 line references. Reproduce the reported race with the Gerrit acceptance tests under ThreadSanitizer, then trace the synchronization around updateDocument and refresh. Done means the concurrent read and write are safely coordinated and the race no longer appears.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
search
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.