Skip to content

Commit d8d61bf

Browse files
ahornaceVladimir Kotal
authored andcommitted
Add time threshold if suggestions are taking too much time
1 parent d8603c1 commit d8d61bf

File tree

2 files changed

+135
-25
lines changed

2 files changed

+135
-25
lines changed

suggester/src/main/java/org/opengrok/suggest/Suggester.java

Lines changed: 118 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@
3838
import java.nio.file.Path;
3939
import java.time.Duration;
4040
import java.time.Instant;
41+
import java.util.ArrayList;
4142
import java.util.Collection;
4243
import java.util.Collections;
4344
import java.util.HashSet;
4445
import java.util.List;
4546
import java.util.Map;
4647
import java.util.Map.Entry;
4748
import java.util.Set;
49+
import java.util.concurrent.Callable;
4850
import java.util.concurrent.ConcurrentHashMap;
4951
import java.util.concurrent.ExecutorService;
5052
import java.util.concurrent.Executors;
@@ -61,6 +63,8 @@ public final class Suggester implements Closeable {
6163

6264
private static final String PROJECTS_DISABLED_KEY = "";
6365

66+
private static final int MAX_TIME_MS = 1000;
67+
6468
private static final Logger logger = Logger.getLogger(Suggester.class.getName());
6569

6670
private final Map<String, SuggesterProjectData> projectData = new ConcurrentHashMap<>();
@@ -79,6 +83,8 @@ public final class Suggester implements Closeable {
7983

8084
private final Set<String> allowedFields;
8185

86+
private final ExecutorService executorService = Executors.newWorkStealingPool();
87+
8288
/**
8389
* @param suggesterDir directory under which the suggester data should be created
8490
* @param resultSize maximum number of suggestions that should be returned
@@ -129,13 +135,13 @@ public void init(final Collection<NamedIndexDir> luceneIndexes) {
129135
synchronized (lock) {
130136
logger.log(Level.INFO, "Initializing suggester");
131137

132-
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
138+
ExecutorService executor = Executors.newWorkStealingPool();
133139

134140
for (NamedIndexDir indexDir : luceneIndexes) {
135-
submitInitIfIndexExists(executorService, indexDir);
141+
submitInitIfIndexExists(executor, indexDir);
136142
}
137143

138-
shutdownAndAwaitTermination(executorService, "Suggester successfully initialized");
144+
shutdownAndAwaitTermination(executor, "Suggester successfully initialized");
139145
}
140146
}
141147

@@ -195,6 +201,7 @@ private void shutdownAndAwaitTermination(final ExecutorService executorService,
195201
logger.log(Level.INFO, logMessageOnSuccess);
196202
} catch (InterruptedException e) {
197203
logger.log(Level.SEVERE, "Interrupted while building suggesters", e);
204+
Thread.currentThread().interrupt();
198205
}
199206
}
200207

@@ -211,18 +218,18 @@ public void rebuild(final Collection<NamedIndexDir> indexDirs) {
211218
synchronized (lock) {
212219
logger.log(Level.INFO, "Rebuilding following suggesters: {0}", indexDirs);
213220

214-
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
221+
ExecutorService executor = Executors.newWorkStealingPool();
215222

216223
for (NamedIndexDir indexDir : indexDirs) {
217224
SuggesterProjectData data = this.projectData.get(indexDir.name);
218225
if (data != null) {
219-
executorService.submit(getRebuildRunnable(data));
226+
executor.submit(getRebuildRunnable(data));
220227
} else {
221-
submitInitIfIndexExists(executorService, indexDir);
228+
submitInitIfIndexExists(executor, indexDir);
222229
}
223230
}
224231

225-
shutdownAndAwaitTermination(executorService, "Suggesters for " + indexDirs + " were successfully rebuilt");
232+
shutdownAndAwaitTermination(executor, "Suggesters for " + indexDirs + " were successfully rebuilt");
226233
}
227234
}
228235

@@ -287,8 +294,21 @@ public List<LookupResultItem> search(
287294
indexReaders.get(0).getReader()));
288295
}
289296

290-
List<LookupResultItem> results = readers.parallelStream().flatMap(namedIndexReader -> {
297+
List<LookupResultItem> results;
298+
if (!SuggesterUtils.isComplexQuery(query, suggesterQuery)) { // use WFST for lone prefix
299+
results = prefixLookup(readers, (SuggesterPrefixQuery) suggesterQuery);
300+
} else {
301+
results = complexLookup(readers, suggesterQuery, query);
302+
}
303+
304+
return SuggesterUtils.combineResults(results, resultSize);
305+
}
291306

307+
private List<LookupResultItem> prefixLookup(
308+
final List<NamedIndexReader> readers,
309+
final SuggesterPrefixQuery suggesterQuery
310+
) {
311+
return readers.parallelStream().flatMap(namedIndexReader -> {
292312
SuggesterProjectData data = projectData.get(namedIndexReader.name);
293313
if (data == null) {
294314
logger.log(Level.FINE, "{0} not yet initialized", namedIndexReader.name);
@@ -300,26 +320,45 @@ public List<LookupResultItem> search(
300320
}
301321

302322
try {
303-
if (!SuggesterUtils.isComplexQuery(query, suggesterQuery)) { // use WFST for lone prefix
304-
String prefix = ((SuggesterPrefixQuery) suggesterQuery).getPrefix().text();
305-
306-
return data.lookup(suggesterQuery.getField(), prefix, resultSize)
307-
.stream()
308-
.map(item -> new LookupResultItem(item.key.toString(), namedIndexReader.name, item.value));
309-
} else {
310-
SuggesterSearcher searcher = new SuggesterSearcher(namedIndexReader.reader, resultSize);
323+
String prefix = suggesterQuery.getPrefix().text();
311324

312-
List<LookupResultItem> resultItems = searcher.suggest(query, namedIndexReader.name, suggesterQuery,
313-
data.getSearchCounts(suggesterQuery.getField()));
314-
315-
return resultItems.stream();
316-
}
325+
return data.lookup(suggesterQuery.getField(), prefix, resultSize)
326+
.stream()
327+
.map(item -> new LookupResultItem(item.key.toString(), namedIndexReader.name, item.value));
317328
} finally {
318329
data.unlock();
319330
}
320331
}).collect(Collectors.toList());
332+
}
321333

322-
return SuggesterUtils.combineResults(results, resultSize);
334+
private List<LookupResultItem> complexLookup(
335+
final List<NamedIndexReader> readers,
336+
final SuggesterQuery suggesterQuery,
337+
final Query query
338+
) {
339+
List<LookupResultItem> results = new ArrayList<>(readers.size() * resultSize);
340+
List<SuggesterSearchTask> searchTasks = new ArrayList<>(readers.size());
341+
for (NamedIndexReader ir : readers) {
342+
searchTasks.add(new SuggesterSearchTask(ir, query, suggesterQuery, results));
343+
}
344+
345+
try {
346+
executorService.invokeAll(searchTasks, MAX_TIME_MS, TimeUnit.MILLISECONDS);
347+
} catch (InterruptedException e) {
348+
logger.log(Level.WARNING, "Interrupted while invoking suggester search", e);
349+
Thread.currentThread().interrupt();
350+
}
351+
352+
// wait for tasks to finish
353+
for (SuggesterSearchTask searchTask : searchTasks) {
354+
if (!searchTask.started) {
355+
continue;
356+
}
357+
// "spin lock" – should be fast since all the tasks either finished or were interrupted
358+
while (!searchTask.finished) {
359+
}
360+
}
361+
return results;
323362
}
324363

325364
/**
@@ -422,6 +461,7 @@ public List<Entry<BytesRef, Integer>> getSearchCounts(
422461
*/
423462
@Override
424463
public void close() {
464+
executorService.shutdownNow();
425465
projectData.values().forEach(f -> {
426466
try {
427467
f.close();
@@ -431,6 +471,62 @@ public void close() {
431471
});
432472
}
433473

474+
private class SuggesterSearchTask implements Callable<Void> {
475+
476+
private final NamedIndexReader namedIndexReader;
477+
private final Query query;
478+
private final SuggesterQuery suggesterQuery;
479+
private final List<LookupResultItem> results;
480+
481+
private volatile boolean finished = false;
482+
private volatile boolean started = false;
483+
484+
SuggesterSearchTask(
485+
final NamedIndexReader namedIndexReader,
486+
final Query query,
487+
final SuggesterQuery suggesterQuery,
488+
final List<LookupResultItem> results
489+
) {
490+
this.namedIndexReader = namedIndexReader;
491+
this.query = query;
492+
this.suggesterQuery = suggesterQuery;
493+
this.results = results;
494+
}
495+
496+
@Override
497+
public Void call() {
498+
try {
499+
started = true;
500+
501+
SuggesterProjectData data = projectData.get(namedIndexReader.name);
502+
if (data == null) {
503+
logger.log(Level.FINE, "{0} not yet initialized", namedIndexReader.name);
504+
return null;
505+
}
506+
boolean gotLock = data.tryLock();
507+
if (!gotLock) { // do not wait for rebuild
508+
return null;
509+
}
510+
511+
try {
512+
SuggesterSearcher searcher = new SuggesterSearcher(namedIndexReader.reader, resultSize);
513+
514+
List<LookupResultItem> resultItems = searcher.suggest(query, namedIndexReader.name, suggesterQuery,
515+
data.getSearchCounts(suggesterQuery.getField()));
516+
517+
synchronized (results) {
518+
results.addAll(resultItems);
519+
}
520+
} finally {
521+
data.unlock();
522+
}
523+
} finally {
524+
finished = true;
525+
}
526+
return null;
527+
}
528+
}
529+
434530
/**
435531
* Model classes for holding project name and path to ist index directory.
436532
*/

suggester/src/main/java/org/opengrok/suggest/SuggesterSearcher.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
import org.opengrok.suggest.query.customized.CustomPhraseQuery;
4848

4949
import java.io.IOException;
50-
import java.util.LinkedList;
50+
import java.util.ArrayList;
51+
import java.util.Collections;
5152
import java.util.List;
5253
import java.util.Set;
5354
import java.util.logging.Level;
@@ -65,6 +66,8 @@ class SuggesterSearcher extends IndexSearcher {
6566

6667
private final int resultSize;
6768

69+
private boolean interrupted;
70+
6871
/**
6972
* @param reader reader of the index for which to provide suggestions
7073
* @param resultSize size of the results
@@ -90,7 +93,7 @@ public List<LookupResultItem> suggest(
9093
final SuggesterQuery suggesterQuery,
9194
final PopularityCounter popularityCounter
9295
) {
93-
List<LookupResultItem> results = new LinkedList<>();
96+
List<LookupResultItem> results = new ArrayList<>(resultSize * leafContexts.size());
9497

9598
Query rewrittenQuery = null;
9699

@@ -104,6 +107,9 @@ public List<LookupResultItem> suggest(
104107
}
105108

106109
for (LeafReaderContext context : this.leafContexts) {
110+
if (interrupted) {
111+
break;
112+
}
107113
try {
108114
results.addAll(suggest(rewrittenQuery, context, project, suggesterQuery, popularityCounter));
109115
} catch (IOException e) {
@@ -125,6 +131,10 @@ private List<LookupResultItem> suggest(
125131
final SuggesterQuery suggesterQuery,
126132
final PopularityCounter searchCounts
127133
) throws IOException {
134+
if (Thread.interrupted()) {
135+
interrupted = true;
136+
return Collections.emptyList();
137+
}
128138

129139
boolean shouldLeaveOutSameTerms = shouldLeaveOutSameTerms(query, suggesterQuery);
130140
Set<BytesRef> tokensAlreadyIncluded = null;
@@ -148,13 +158,17 @@ private List<LookupResultItem> suggest(
148158

149159
LookupPriorityQueue queue = new LookupPriorityQueue(resultSize);
150160

151-
152161
boolean needPositionsAndFrequencies = needPositionsAndFrequencies(query);
153162

154163
PostingsEnum postingsEnum = null;
155164

156165
BytesRef term = termsEnum.next();
157166
while (term != null) {
167+
if (Thread.interrupted()) {
168+
interrupted = true;
169+
break;
170+
}
171+
158172
if (needPositionsAndFrequencies) {
159173
postingsEnum = termsEnum.postings(postingsEnum, PostingsEnum.POSITIONS | PostingsEnum.FREQS);
160174
} else {

0 commit comments

Comments
 (0)