mirror of https://github.com/halo-dev/halo
Fix the problem that Lucene lock is held by VM (#6570)
#### What type of PR is this? /kind bug /area core /milestone 2.20.x #### What this PR does / why we need it: This PR add keyword synchronized for methods `addOrUpdateDocuments`, `deleteDocuments` and `deleteAll` to ensure the write lock of Lucene is obtained only by one IndexWriter at the same time. #### Which issue(s) this PR fixes: Fixes #6569 #### Does this PR introduce a user-facing change? ```release-note 修复重启后无法搜索部分文档的问题 ```pull/6580/head
parent
7281a48325
commit
19de4db273
|
@ -84,7 +84,7 @@ public class LuceneSearchEngine implements SearchEngine, InitializingBean, Dispo
|
|||
|
||||
private Directory directory;
|
||||
|
||||
public LuceneSearchEngine(Path indexRootDir) throws IOException {
|
||||
public LuceneSearchEngine(Path indexRootDir) {
|
||||
this.indexRootDir = indexRootDir;
|
||||
}
|
||||
|
||||
|
@ -106,12 +106,14 @@ public class LuceneSearchEngine implements SearchEngine, InitializingBean, Dispo
|
|||
|
||||
var writerConfig = new IndexWriterConfig(this.analyzer)
|
||||
.setOpenMode(CREATE_OR_APPEND);
|
||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||
indexWriter.updateDocuments(deleteQuery, docs);
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.propagate(e);
|
||||
} finally {
|
||||
this.refreshSearcherManager();
|
||||
synchronized (this) {
|
||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||
indexWriter.updateDocuments(deleteQuery, docs);
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.propagate(e);
|
||||
} finally {
|
||||
this.refreshSearcherManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,12 +124,14 @@ public class LuceneSearchEngine implements SearchEngine, InitializingBean, Dispo
|
|||
var deleteQuery = new TermInSetQuery("id", terms);
|
||||
var writerConfig = new IndexWriterConfig(this.analyzer)
|
||||
.setOpenMode(CREATE_OR_APPEND);
|
||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||
indexWriter.deleteDocuments(deleteQuery);
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.propagate(e);
|
||||
} finally {
|
||||
this.refreshSearcherManager();
|
||||
synchronized (this) {
|
||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||
indexWriter.deleteDocuments(deleteQuery);
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.propagate(e);
|
||||
} finally {
|
||||
this.refreshSearcherManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,12 +139,14 @@ public class LuceneSearchEngine implements SearchEngine, InitializingBean, Dispo
|
|||
public void deleteAll() {
|
||||
var writerConfig = new IndexWriterConfig(this.analyzer)
|
||||
.setOpenMode(CREATE_OR_APPEND);
|
||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||
indexWriter.deleteAll();
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.propagate(e);
|
||||
} finally {
|
||||
this.refreshSearcherManager();
|
||||
synchronized (this) {
|
||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||
indexWriter.deleteAll();
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.propagate(e);
|
||||
} finally {
|
||||
this.refreshSearcherManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,19 @@ package run.halo.app.search.lucene;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.IntStream;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.store.AlreadyClosedException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -76,6 +83,35 @@ class LuceneSearchEngineTest {
|
|||
assertEquals(0, reader.getDocCount("id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddOrUpdateDocumentConcurrently()
|
||||
throws ExecutionException, InterruptedException, TimeoutException {
|
||||
runConcurrently(() -> {
|
||||
var haloDoc = createFakeHaloDoc();
|
||||
searchEngine.addOrUpdate(List.of(haloDoc));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteDocumentConcurrently()
|
||||
throws ExecutionException, InterruptedException, TimeoutException {
|
||||
runConcurrently(() -> {
|
||||
var haloDoc = createFakeHaloDoc();
|
||||
searchEngine.addOrUpdate(List.of(haloDoc));
|
||||
searchEngine.deleteDocument(List.of(haloDoc.getId()));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteAllConcurrently()
|
||||
throws ExecutionException, InterruptedException, TimeoutException {
|
||||
runConcurrently(() -> {
|
||||
var haloDoc = createFakeHaloDoc();
|
||||
searchEngine.addOrUpdate(List.of(haloDoc));
|
||||
searchEngine.deleteAll();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDestroy() throws Exception {
|
||||
var directory = this.searchEngine.getDirectory();
|
||||
|
@ -118,6 +154,17 @@ class LuceneSearchEngineTest {
|
|||
assertEquals("<fake-tag>fake</fake-tag>-content", gotHaloDoc.getContent());
|
||||
}
|
||||
|
||||
void runConcurrently(Runnable runnable)
|
||||
throws ExecutionException, InterruptedException, TimeoutException {
|
||||
var executorService = Executors.newFixedThreadPool(10);
|
||||
var futures = IntStream.of(0, 10)
|
||||
.mapToObj(i -> CompletableFuture.runAsync(runnable, executorService))
|
||||
.toArray(CompletableFuture[]::new);
|
||||
CompletableFuture.allOf(futures).get(10, TimeUnit.SECONDS);
|
||||
executorService.shutdownNow();
|
||||
assertTrue(executorService.awaitTermination(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
HaloDocument createFakeHaloDoc() {
|
||||
var haloDoc = new HaloDocument();
|
||||
haloDoc.setId("fake-id");
|
||||
|
|
Loading…
Reference in New Issue