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;
|
private Directory directory;
|
||||||
|
|
||||||
public LuceneSearchEngine(Path indexRootDir) throws IOException {
|
public LuceneSearchEngine(Path indexRootDir) {
|
||||||
this.indexRootDir = indexRootDir;
|
this.indexRootDir = indexRootDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,12 +106,14 @@ public class LuceneSearchEngine implements SearchEngine, InitializingBean, Dispo
|
||||||
|
|
||||||
var writerConfig = new IndexWriterConfig(this.analyzer)
|
var writerConfig = new IndexWriterConfig(this.analyzer)
|
||||||
.setOpenMode(CREATE_OR_APPEND);
|
.setOpenMode(CREATE_OR_APPEND);
|
||||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
synchronized (this) {
|
||||||
indexWriter.updateDocuments(deleteQuery, docs);
|
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||||
} catch (IOException e) {
|
indexWriter.updateDocuments(deleteQuery, docs);
|
||||||
throw Exceptions.propagate(e);
|
} catch (IOException e) {
|
||||||
} finally {
|
throw Exceptions.propagate(e);
|
||||||
this.refreshSearcherManager();
|
} finally {
|
||||||
|
this.refreshSearcherManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,12 +124,14 @@ public class LuceneSearchEngine implements SearchEngine, InitializingBean, Dispo
|
||||||
var deleteQuery = new TermInSetQuery("id", terms);
|
var deleteQuery = new TermInSetQuery("id", terms);
|
||||||
var writerConfig = new IndexWriterConfig(this.analyzer)
|
var writerConfig = new IndexWriterConfig(this.analyzer)
|
||||||
.setOpenMode(CREATE_OR_APPEND);
|
.setOpenMode(CREATE_OR_APPEND);
|
||||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
synchronized (this) {
|
||||||
indexWriter.deleteDocuments(deleteQuery);
|
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||||
} catch (IOException e) {
|
indexWriter.deleteDocuments(deleteQuery);
|
||||||
throw Exceptions.propagate(e);
|
} catch (IOException e) {
|
||||||
} finally {
|
throw Exceptions.propagate(e);
|
||||||
this.refreshSearcherManager();
|
} finally {
|
||||||
|
this.refreshSearcherManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,12 +139,14 @@ public class LuceneSearchEngine implements SearchEngine, InitializingBean, Dispo
|
||||||
public void deleteAll() {
|
public void deleteAll() {
|
||||||
var writerConfig = new IndexWriterConfig(this.analyzer)
|
var writerConfig = new IndexWriterConfig(this.analyzer)
|
||||||
.setOpenMode(CREATE_OR_APPEND);
|
.setOpenMode(CREATE_OR_APPEND);
|
||||||
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
synchronized (this) {
|
||||||
indexWriter.deleteAll();
|
try (var indexWriter = new IndexWriter(this.directory, writerConfig)) {
|
||||||
} catch (IOException e) {
|
indexWriter.deleteAll();
|
||||||
throw Exceptions.propagate(e);
|
} catch (IOException e) {
|
||||||
} finally {
|
throw Exceptions.propagate(e);
|
||||||
this.refreshSearcherManager();
|
} 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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.index.DirectoryReader;
|
||||||
import org.apache.lucene.store.AlreadyClosedException;
|
import org.apache.lucene.store.AlreadyClosedException;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
@ -76,6 +83,35 @@ class LuceneSearchEngineTest {
|
||||||
assertEquals(0, reader.getDocCount("id"));
|
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
|
@Test
|
||||||
void shouldDestroy() throws Exception {
|
void shouldDestroy() throws Exception {
|
||||||
var directory = this.searchEngine.getDirectory();
|
var directory = this.searchEngine.getDirectory();
|
||||||
|
@ -118,6 +154,17 @@ class LuceneSearchEngineTest {
|
||||||
assertEquals("<fake-tag>fake</fake-tag>-content", gotHaloDoc.getContent());
|
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() {
|
HaloDocument createFakeHaloDoc() {
|
||||||
var haloDoc = new HaloDocument();
|
var haloDoc = new HaloDocument();
|
||||||
haloDoc.setId("fake-id");
|
haloDoc.setId("fake-id");
|
||||||
|
|
Loading…
Reference in New Issue