Skip to content

Commit e72b68f

Browse files
committed
Fix race condition when adding docs with same pid.
When two threads were both adding a document to the index with the same persistent identifier, and the 'replace' strategy is selected, it's possible to decide that a document is already in the index (its pid is in the set of pids) while indexWriter.addDocument() had not been called yet. The entire add/update section must be synchronized to ensure atomicity.
1 parent 6a03c3f commit e72b68f

1 file changed

Lines changed: 70 additions & 64 deletions

File tree

engine/src/main/java/nl/inl/blacklab/index/BLIndexWriterProxyLucene.java

Lines changed: 70 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -78,74 +78,37 @@ private Term getPidTerm(BLInputDocument document) {
7878
@Override
7979
public void addDocument(BLInputDocument document) throws IOException {
8080
// Do we have a persistent identifier?
81+
Document doc = luceneDoc(document);
8182
if (getPidFieldName() != null) {
82-
// Yes; does this pid already exist in the corpus?
83-
Term pid = getPidTerm(document);
84-
if (!addToPids(pid.text())) {
85-
// Already exists; handle according to configuration
86-
switch (index.getIfDocumentExists()) {
87-
case UPSERT:
88-
indexWriter.updateDocument(pid, luceneDoc(document));
89-
break;
90-
case SKIP:
91-
/* do nothing */
92-
break;
93-
case FAIL:
94-
throw new ErrorIndexingFile("Document with pid '" + pid +
95-
"' already exists in corpus; cannot add it again " +
96-
"(ifDocumentExists setting set to 'fail'; set to 'replace' to upsert instead)");
97-
default:
98-
throw new IllegalArgumentException();
99-
}
100-
return;
101-
}
83+
// We have a persistent identifier; ensure it only occurs once in the corpus.
84+
Term pidTerm = getPidTerm(document);
85+
BlackLabIndexWriter.IfDocumentExists ifDocumentExists = index.getIfDocumentExists();
86+
addOrUpdate(doc, pidTerm, ifDocumentExists);
87+
} else {
88+
// We don't have persistent identifiers. Just add the document.
89+
indexWriter.addDocument(doc);
10290
}
103-
// We don't have persistent identifiers, or this pid doesn't occur in the corpus yet.
104-
// Just add the document.
105-
indexWriter.addDocument(luceneDoc(document));
106-
}
107-
108-
private Document luceneDoc(BLInputDocument document) {
109-
return ((BLInputDocumentLucene)document).getDocument();
11091
}
11192

112-
@Override
113-
public void close() throws IOException {
114-
indexWriter.close();
115-
}
116-
117-
@Override
118-
public void commit() throws IOException {
119-
indexWriter.commit();
120-
}
121-
122-
@Override
123-
public void rollback() throws IOException {
124-
indexWriter.rollback();
125-
}
126-
127-
@Override
128-
public boolean isOpen() {
129-
return indexWriter.isOpen();
130-
}
131-
132-
public IndexWriter getWriter() {
133-
return indexWriter;
134-
}
135-
136-
@Override
137-
public void deleteDocuments(Query q) throws IOException {
138-
indexWriter.deleteDocuments(q);
139-
}
140-
141-
@Override
142-
public long updateDocument(Term term, BLInputDocument document) throws IOException {
143-
return indexWriter.updateDocument(term, luceneDoc(document));
144-
}
145-
146-
@Override
147-
public int getNumberOfDocs() {
148-
return indexWriter.getDocStats().numDocs;
93+
/**
94+
* Atomically add or update document (or skip/fail, depending on config).
95+
*/
96+
private synchronized void addOrUpdate(Document doc, Term pidTerm,
97+
BlackLabIndexWriter.IfDocumentExists ifDocumentExists) throws IOException {
98+
if (!addToPids(pidTerm.text())) {
99+
// Already exists; handle according to configuration
100+
switch (ifDocumentExists) {
101+
case UPSERT -> indexWriter.updateDocument(pidTerm, doc);
102+
case SKIP -> { /* do nothing */ }
103+
case FAIL -> throw new ErrorIndexingFile("Document with pid '" + pidTerm.text() +
104+
"' already exists in corpus; cannot add it again " +
105+
"(ifDocumentExists setting set to 'fail'; set to 'replace' to upsert instead)");
106+
default -> throw new IllegalArgumentException();
107+
}
108+
} else {
109+
// Not in the index yet; add it now.
110+
indexWriter.addDocument(doc);
111+
}
149112
}
150113

151114
/**
@@ -194,4 +157,47 @@ public Status needsField(FieldInfo fieldInfo) {
194157
return usedPids.add(pid);
195158
}
196159

160+
private Document luceneDoc(BLInputDocument document) {
161+
return ((BLInputDocumentLucene)document).getDocument();
162+
}
163+
164+
@Override
165+
public void close() throws IOException {
166+
indexWriter.close();
167+
}
168+
169+
@Override
170+
public void commit() throws IOException {
171+
indexWriter.commit();
172+
}
173+
174+
@Override
175+
public void rollback() throws IOException {
176+
indexWriter.rollback();
177+
}
178+
179+
@Override
180+
public boolean isOpen() {
181+
return indexWriter.isOpen();
182+
}
183+
184+
public IndexWriter getWriter() {
185+
return indexWriter;
186+
}
187+
188+
@Override
189+
public void deleteDocuments(Query q) throws IOException {
190+
indexWriter.deleteDocuments(q);
191+
}
192+
193+
@Override
194+
public long updateDocument(Term term, BLInputDocument document) throws IOException {
195+
return indexWriter.updateDocument(term, luceneDoc(document));
196+
}
197+
198+
@Override
199+
public int getNumberOfDocs() {
200+
return indexWriter.getDocStats().numDocs;
201+
}
202+
197203
}

0 commit comments

Comments
 (0)