Use Fork/Join framework for lucene search and index

Change-Id: I024fa0dc09f7443a0079155f0670a839360960ad
Signed-off-by: Dawid Pakuła <zulus@w3des.net>
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexContainer.java b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexContainer.java
index 2126fff..6b966b1 100644
--- a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexContainer.java
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexContainer.java
@@ -16,8 +16,9 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 import org.apache.lucene.analysis.core.SimpleAnalyzer;
 import org.apache.lucene.index.ConcurrentMergeScheduler;
@@ -28,13 +29,12 @@
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.SearcherFactory;
 import org.apache.lucene.search.SearcherManager;
-import org.apache.lucene.store.AlreadyClosedException;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.SingleInstanceLockFactory;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.dltk.core.index.lucene.LucenePlugin;
 
@@ -129,7 +129,8 @@
 
 	private IndexWriter createWriter(Path path) throws IOException {
 
-		Directory indexDir = FSDirectory.open(path);
+		Directory indexDir = FSDirectory.open(path,
+				new SingleInstanceLockFactory());
 		purgeLocks(path);
 		IndexWriterConfig config = new IndexWriterConfig(new SimpleAnalyzer());
 		config.setUseCompoundFile(false);
@@ -138,7 +139,6 @@
 		config.setMergeScheduler(mergeScheduler);
 		config.setOpenMode(OpenMode.CREATE_OR_APPEND);
 		config.setCommitOnClose(false);
-		config.setCheckPendingFlushUpdate(false);
 		return new IndexWriter(indexDir, config);
 	}
 
@@ -171,14 +171,21 @@
 		return fTimestampsWriter;
 	}
 
-	public synchronized SearcherManager getTimestampsSearcher() {
+	public SearcherManager getTimestampsSearcher() {
 		try {
 			if (fTimestampsSearcher == null) {
-				fTimestampsSearcher = new SearcherManager(getTimestampsWriter(),
-						true, false, new SearcherFactory());
+				synchronized (this) {
+					if (fTimestampsSearcher == null) {
+						fTimestampsSearcher = new SearcherManager(
+								getTimestampsWriter(), true, false,
+								new SearcherFactory());
+					} else {
+						fTimestampsSearcher.maybeRefresh();
+					}
+				}
+
 			}
-			// Try to achieve the up-to-date index state
-			fTimestampsSearcher.maybeRefresh();
+
 		} catch (IOException e) {
 			Logger.logException(e);
 		}
@@ -193,32 +200,41 @@
 	public IndexWriter getIndexWriter(IndexType dataType, int elementType) {
 		IndexWriter writer = fIndexWriters.get(dataType).get(elementType);
 		if (writer == null) {
-			Path writerPath = getPath(dataType, elementType);
-			writer = getWriter(writerPath);
-			fIndexWriters.get(dataType).put(elementType, writer);
-			fIndexSearchers.get(dataType).put(elementType, null);
+			synchronized (this) {
+				writer = fIndexWriters.get(dataType).get(elementType);
+				if (writer == null) {
+					Path writerPath = getPath(dataType, elementType);
+					writer = getWriter(writerPath);
+					fIndexWriters.get(dataType).put(elementType, writer);
+					fIndexSearchers.get(dataType).put(elementType, null);
+				}
+			}
 		}
 		return writer;
 	}
 
-	public synchronized SearcherManager getIndexSearcher(IndexType dataType,
+	public SearcherManager getIndexSearcher(IndexType dataType,
 			int elementType) {
 
 		SearcherManager searcher = fIndexSearchers.get(dataType)
 				.get(elementType);
 		try {
-			if (searcher != null) {
-				try {
-					searcher.maybeRefresh();
-				} catch (AlreadyClosedException closed) {
-					searcher = null;
-				}
-			}
 			if (searcher == null) {
-				searcher = new SearcherManager(
-						FSDirectory.open(getPath(dataType, elementType)),
-						new SearcherFactory());
-				fIndexSearchers.get(dataType).put(elementType, searcher);
+				synchronized (this) {
+					searcher = fIndexSearchers.get(dataType).get(elementType);
+					if (searcher == null) {
+						searcher = new SearcherManager(
+								getIndexWriter(dataType, elementType),
+								new SearcherFactory());
+						fIndexSearchers.get(dataType).put(elementType,
+								searcher);
+					} else {
+						searcher.maybeRefresh();
+					}
+
+				}
+			} else {
+				searcher.maybeRefresh();
 			}
 
 		} catch (IndexNotFoundException e) {
@@ -290,33 +306,27 @@
 		return false;
 	}
 
-	void commit(IProgressMonitor monitor) {
-		int ticks = 1;
-		for (Map<?, ?> dataWriters : fIndexWriters.values()) {
-			ticks += dataWriters.size();
-		}
-		SubMonitor subMonitor = SubMonitor.convert(monitor, ticks);
-		try {
-			for (Entry<IndexType, Map<Integer, IndexWriter>> entry : fIndexWriters
-					.entrySet()) {
-				Map<Integer, IndexWriter> dataWriters = entry.getValue();
-				for (Entry<Integer, IndexWriter> writerEntry : dataWriters
-						.entrySet()) {
-					IndexWriter writer = writerEntry.getValue();
-					if (writer != null && !subMonitor.isCanceled()) {
-						writer.commit();
-						subMonitor.worked(1);
+	void commit() {
+		List<IndexWriter> writers = new LinkedList<>();
+		synchronized (this) {
+			for (Map<Integer, IndexWriter> dataWriters : fIndexWriters
+					.values()) {
+				for (IndexWriter writer : dataWriters.values()) {
+					if (writer != null) {
+						writers.add(writer);
 					}
 				}
 			}
-			if (fTimestampsWriter != null && !subMonitor.isCanceled()) {
-				fTimestampsWriter.commit();
-				subMonitor.worked(1);
+		}
+		try {
+			for (IndexWriter writer : writers) {
+				writer.commit();
 			}
-			subMonitor.done();
+			if (fTimestampsWriter != null) {
+				fTimestampsWriter.commit();
+			}
 		} catch (IOException e) {
 			Logger.logException(e);
 		}
 	}
-
 }
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/LuceneManager.java b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/LuceneManager.java
index f1dfa8b..e73a96e 100644
--- a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/LuceneManager.java
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/LuceneManager.java
@@ -19,12 +19,14 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ForkJoinTask;
 
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.search.SearcherManager;
@@ -95,16 +97,24 @@
 			SubMonitor subMonitor = SubMonitor.convert(monitor,
 					containersNumber);
 			try {
-				for (IndexContainer indexContainer : dirtyContainers) {
-					if (!monitor.isCanceled()) {
-						// Commit index data without merging deletions (better
-						// performance)
-						indexContainer.commit(subMonitor.newChild(1));
-					}
+				if (subMonitor.isCanceled()) {
+					return Status.CANCEL_STATUS;
 				}
-				monitor.done();
+				List<ForkJoinTask> tasks = new LinkedList<>();
+				if (dirtyContainers.size() == 1) {
+					dirtyContainers.get(0).commit();
+				} else {
+					for (IndexContainer indexContainer : dirtyContainers) {
+						tasks.add(ForkJoinTask.adapt(() -> {
+							indexContainer.commit();
+						}));
+					}
+					ForkJoinTask.invokeAll(tasks);
+				}
 			} catch (Exception e) {
 				Logger.logException(e);
+			} finally {
+				subMonitor.done();
 			}
 			return Status.OK_STATUS;
 		}
@@ -292,14 +302,15 @@
 		return fIndexContainers.get(containerId);
 	}
 
-	private synchronized void deleteIndexContainer(String container,
-			boolean wait) {
-		String containerId = (String) fContainerMappings.remove(container);
-		if (containerId != null) {
-			IndexContainer containerEntry = fIndexContainers
-					.remove(containerId);
-			saveMappings();
-			containerEntry.delete(wait);
+	private void deleteIndexContainer(String container, boolean wait) {
+		synchronized (fContainerMappings) {
+			String containerId = (String) fContainerMappings.remove(container);
+			if (containerId != null) {
+				IndexContainer containerEntry = fIndexContainers
+						.remove(containerId);
+				saveMappings();
+				containerEntry.delete(wait);
+			}
 		}
 	}
 
@@ -455,5 +466,4 @@
 		}
 		indexRoot.toFile().mkdir();
 	}
-
 }
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/LuceneSearchEngine.java b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/LuceneSearchEngine.java
index a5ce77d..30b5806 100644
--- a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/LuceneSearchEngine.java
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/LuceneSearchEngine.java
@@ -30,9 +30,13 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.RecursiveTask;
 
 import org.apache.lucene.index.BinaryDocValues;
 import org.apache.lucene.index.LeafReader;
@@ -121,13 +125,15 @@
 		private Map<String, BinaryDocValues> fDocBinaryValues;
 		private String fContainer;
 		private int fElementType;
-		private List<SearchMatch> fResult;
+		private List<SearchMatch> fResult = new LinkedList<>();
 
-		public ResultsCollector(String container, int elementType,
-				List<SearchMatch> result) {
+		public ResultsCollector(String container, int elementType) {
 			this.fContainer = container;
 			this.fElementType = elementType;
-			this.fResult = result;
+		}
+
+		public List<SearchMatch> getfResult() {
+			return fResult;
 		}
 
 		@Override
@@ -235,24 +241,38 @@
 				|| searchFor == SearchFor.ALL_OCCURRENCES;
 		boolean searchForRefs = searchFor == SearchFor.REFERENCES
 				|| searchFor == SearchFor.ALL_OCCURRENCES;
+
+		List<SearchTask> tasks = new LinkedList<>();
+		List<String> containers = SearchScope.getContainers(scope);
+		List<String> scripts = SearchScope.getScripts(scope);
+		final SearchMatchHandler searchMatchHandler = new SearchMatchHandler(
+				scope, requestor);
 		if (searchForRefs) {
-			doSearch(elementType, qualifier, elementName, parent, trueFlags,
-					falseFlags, limit, true, matchRule, scope, requestor,
-					monitor);
+			for (String container : containers) {
+				tasks.add(new SearchTask(elementType, qualifier, elementName,
+						parent, trueFlags, falseFlags, true, matchRule, scripts,
+						container));
+			}
+			ForkJoinTask.invokeAll(tasks).stream().forEach(t -> t.join()
+					.stream().forEach(m -> searchMatchHandler.handle(m, true)));
+
 		}
 		if (searchForDecls) {
-			doSearch(elementType, qualifier, elementName, parent, trueFlags,
-					falseFlags, limit, false, matchRule, scope, requestor,
-					monitor);
+			for (String container : containers) {
+				tasks.add(new SearchTask(elementType, qualifier, elementName,
+						parent, trueFlags, falseFlags, false, matchRule,
+						scripts, container));
+			}
+			ForkJoinTask.invokeAll(tasks).stream().forEach(t -> t.join()
+					.stream().forEach(m -> searchMatchHandler.handle(m, true)));
 		}
 	}
 
 	private Query createQuery(final String elementName, final String qualifier,
 			final String parent, final int trueFlags, final int falseFlags,
 			final boolean searchForRefs, MatchRule matchRule,
-			IDLTKSearchScope scope) {
+			List<String> scripts) {
 		BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder();
-		List<String> scripts = SearchScope.getScripts(scope);
 		if (!scripts.isEmpty()) {
 			BooleanQuery.Builder scriptQueryBuilder = new BooleanQuery.Builder();
 			for (String script : scripts) {
@@ -297,35 +317,58 @@
 		return query.clauses().isEmpty() ? null : query;
 	}
 
-	private void doSearch(final int elementType, String qualifier,
-			String elementName, String parent, final int trueFlags,
-			final int falseFlags, int limit, final boolean searchForRefs,
-			MatchRule matchRule, IDLTKSearchScope scope,
-			ISearchRequestor requestor, IProgressMonitor monitor) {
-		Query query = createQuery(elementName, qualifier, parent, trueFlags,
-				falseFlags, searchForRefs, matchRule, scope);
-		IndexSearcher indexSearcher = null;
-		final SearchMatchHandler searchMatchHandler = new SearchMatchHandler(
-				scope, requestor);
-		List<SearchMatch> results = new ArrayList<>();
-		for (String container : SearchScope.getContainers(scope)) {
+	private class SearchTask extends RecursiveTask<List<SearchMatch>> {
+		int elementType;
+		String qualifier;
+		String elementName;
+		String parent;
+		int trueFlags;
+		int falseFlags;
+		boolean searchForRefs;
+		MatchRule matchRule;
+		List<String> scripts;
+		String container;
+
+		private SearchTask(int elementType, String qualifier,
+				String elementName, String parent, int trueFlags,
+				final int falseFlags, boolean searchForRefs,
+				MatchRule matchRule, List<String> scripts, String container) {
+			this.elementType = elementType;
+			this.qualifier = qualifier;
+			this.elementName = elementName;
+			this.parent = parent;
+			this.trueFlags = trueFlags;
+			this.falseFlags = falseFlags;
+			this.searchForRefs = searchForRefs;
+			this.matchRule = matchRule;
+			this.scripts = scripts;
+			this.container = container;
+		}
+
+		@Override
+		protected List<SearchMatch> compute() {
 			SearcherManager searcherManager = LuceneManager.INSTANCE
 					.findIndexSearcher(container,
 							searchForRefs ? IndexType.REFERENCES
 									: IndexType.DECLARATIONS,
 							elementType);
 			if (searcherManager == null) {
-				continue;
+				return Collections.emptyList();
 			}
+			IndexSearcher indexSearcher = null;
 			try {
 				indexSearcher = searcherManager.acquire();
+				Query query = createQuery(elementName, qualifier, parent,
+						trueFlags, falseFlags, searchForRefs, matchRule,
+						scripts);
 				ResultsCollector collector = new ResultsCollector(container,
-						elementType, results);
+						elementType);
 				if (query != null) {
 					indexSearcher.search(query, collector);
 				} else {
 					indexSearcher.search(new MatchAllDocsQuery(), collector);
 				}
+				return collector.getfResult();
 			} catch (IOException e) {
 				Logger.logException(e);
 			} finally {
@@ -337,10 +380,7 @@
 					}
 				}
 			}
-		}
-		// Pass results to entity handler
-		for (SearchMatch result : results) {
-			searchMatchHandler.handle(result, searchForRefs);
+			return Collections.emptyList();
 		}
 	}