Skip to content

Commit d0fd176

Browse files
committed
reopen an index if Lucene closed it for any reason
closes #5521
1 parent 1051692 commit d0fd176

File tree

4 files changed

+108
-2
lines changed

4 files changed

+108
-2
lines changed

nouveau/src/main/java/org/apache/couchdb/nouveau/core/Index.java

+6
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ public synchronized boolean isDeleteOnClose() {
129129
return deleteOnClose;
130130
}
131131

132+
public final boolean isOpen() {
133+
return doIsOpen();
134+
}
135+
136+
protected abstract boolean doIsOpen();
137+
132138
public synchronized void setDeleteOnClose(final boolean deleteOnClose) {
133139
this.deleteOnClose = deleteOnClose;
134140
}

nouveau/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,21 @@ public <R> R with(final String name, final IndexFunction<Index, R> indexFun)
119119
// CachedData pattern from ReentrantReadWriteLock javadoc
120120
holder.lock.readLock().lock();
121121

122-
// Load if not already loaded.
123-
if (holder.state == HolderState.NOT_LOADED) {
122+
// Load if not already loaded or remove if Lucene closed the index elsewhere.
123+
if (holder.state == HolderState.NOT_LOADED
124+
|| (holder.state == HolderState.LOADED && !holder.index.isOpen())) {
124125
holder.lock.readLock().unlock();
125126
holder.lock.writeLock().lock();
126127
try {
128+
if (holder.state == HolderState.LOADED && !holder.index.isOpen()) {
129+
LOGGER.info("removing closed index {}", name);
130+
holder.state = HolderState.UNLOADED;
131+
holder.index = null;
132+
synchronized (cache) {
133+
cache.remove(name, holder);
134+
}
135+
continue retry;
136+
}
127137
if (holder.state == HolderState.NOT_LOADED) {
128138
holder.index = load(name);
129139
holder.commitFuture = this.schedulerExecutorService.scheduleWithFixedDelay(

nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java

+5
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ public void doClose() throws IOException {
190190
});
191191
}
192192

193+
@Override
194+
public boolean doIsOpen() {
195+
return writer.isOpen();
196+
}
197+
193198
@Override
194199
public SearchResults doSearch(final SearchRequest request) throws IOException {
195200
final Query query = parse(request);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package org.apache.couchdb.nouveau.core;
15+
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
18+
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import java.nio.file.Path;
20+
import java.util.concurrent.Executors;
21+
import java.util.concurrent.ForkJoinPool;
22+
import java.util.concurrent.ScheduledExecutorService;
23+
import java.util.concurrent.TimeUnit;
24+
import org.apache.couchdb.nouveau.api.IndexDefinition;
25+
import org.apache.couchdb.nouveau.api.SearchRequest;
26+
import org.apache.couchdb.nouveau.lucene9.ParallelSearcherFactory;
27+
import org.junit.jupiter.api.AfterAll;
28+
import org.junit.jupiter.api.BeforeAll;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.api.io.TempDir;
31+
32+
public class IndexManagerTest {
33+
34+
private static IndexManager manager;
35+
private static ScheduledExecutorService executorService;
36+
37+
@BeforeAll
38+
public static void setupManager(@TempDir Path path) throws Exception {
39+
executorService = Executors.newScheduledThreadPool(2);
40+
41+
manager = new IndexManager();
42+
manager.setRootDir(path);
43+
manager.setObjectMapper(new ObjectMapper());
44+
manager.setCommitIntervalSeconds(10);
45+
manager.setScheduledExecutorService(executorService);
46+
manager.setSearcherFactory(new ParallelSearcherFactory(ForkJoinPool.commonPool()));
47+
manager.start();
48+
}
49+
50+
@AfterAll
51+
public static void cleanup() throws Exception {
52+
executorService.shutdownNow();
53+
executorService.awaitTermination(5, TimeUnit.SECONDS);
54+
manager.stop();
55+
}
56+
57+
@Test
58+
public void managerReturnsUsableIndex() throws Exception {
59+
final IndexDefinition indexDefinition = new IndexDefinition();
60+
indexDefinition.setDefaultAnalyzer("standard");
61+
manager.create("foo", indexDefinition);
62+
var searchRequest = new SearchRequest();
63+
searchRequest.setQuery("*:*");
64+
var searchResults = manager.with("foo", (index) -> index.search(searchRequest));
65+
assertThat(searchResults.getTotalHits()).isEqualTo(0);
66+
}
67+
68+
@Test
69+
public void managerReopensAClosedIndex() throws Exception {
70+
final IndexDefinition indexDefinition = new IndexDefinition();
71+
indexDefinition.setDefaultAnalyzer("standard");
72+
73+
manager.create("bar", indexDefinition);
74+
75+
manager.with("bar", (index) -> {
76+
index.close();
77+
return null;
78+
});
79+
80+
final boolean isOpen = manager.with("bar", (index) -> {
81+
return index.isOpen();
82+
});
83+
assertThat(isOpen);
84+
}
85+
}

0 commit comments

Comments
 (0)