bug 191522: provide full text search functionality over task comments
https://bugs.eclipse.org/bugs/show_bug.cgi?id=191522
improvements to thread safety and concurrency model
diff --git a/org.eclipse.mylyn.tasks.index.core/src/org/eclipse/mylyn/internal/tasks/index/core/TaskListIndex.java b/org.eclipse.mylyn.tasks.index.core/src/org/eclipse/mylyn/internal/tasks/index/core/TaskListIndex.java
index 2fae2b5..5e2cd48 100644
--- a/org.eclipse.mylyn.tasks.index.core/src/org/eclipse/mylyn/internal/tasks/index/core/TaskListIndex.java
+++ b/org.eclipse.mylyn.tasks.index.core/src/org/eclipse/mylyn/internal/tasks/index/core/TaskListIndex.java
@@ -25,6 +25,9 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import org.apache.lucene.document.DateTools;
@@ -198,6 +201,8 @@
private int maxMatchSearchHits = 1500;
+ private final ReadWriteLock indexReaderLock = new ReentrantReadWriteLock(true);
+
private TaskListIndex(TaskList taskList, TaskDataManager dataManager) {
if (taskList == null) {
throw new IllegalArgumentException();
@@ -323,11 +328,14 @@
if (patternString.equals(COMMAND_RESET_INDEX)) {
reindex();
}
- IndexReader indexReader = getIndexReader();
- if (indexReader != null) {
- Set<String> hits;
+ Lock readLock = indexReaderLock.readLock();
+ readLock.lock();
+ try {
- synchronized (indexReader) {
+ IndexReader indexReader = getIndexReader();
+ if (indexReader != null) {
+ Set<String> hits;
+
if (lastResults == null || (lastPatternString == null || !lastPatternString.equals(patternString))) {
this.lastPatternString = patternString;
@@ -359,15 +367,18 @@
} else {
hits = lastResults;
}
- }
- synchronized (this) {
- if (this.indexReader == indexReader) {
- this.lastPatternString = patternString;
- this.lastResults = hits;
+ synchronized (this) {
+ if (this.indexReader == indexReader) {
+ this.lastPatternString = patternString;
+ this.lastResults = hits;
+ }
}
+ String taskIdentifier = task.getHandleIdentifier();
+ return hits != null && hits.contains(taskIdentifier);
}
- String taskIdentifier = task.getHandleIdentifier();
- return hits != null && hits.contains(taskIdentifier);
+
+ } finally {
+ readLock.unlock();
}
return false;
}
@@ -392,30 +403,37 @@
}
public void find(String patternString, TaskCollector collector, int resultsLimit) {
- IndexReader indexReader = getIndexReader();
- if (indexReader != null) {
- IndexSearcher indexSearcher = new IndexSearcher(indexReader);
- try {
- Query query = computeQuery(patternString);
- TopDocs results = indexSearcher.search(query, resultsLimit);
- for (ScoreDoc scoreDoc : results.scoreDocs) {
- Document document = indexReader.document(scoreDoc.doc);
- String taskIdentifier = document.get(IndexField.IDENTIFIER.fieldName());
- AbstractTask task = taskList.getTask(taskIdentifier);
- if (task != null) {
- collector.collect(task);
+
+ Lock readLock = indexReaderLock.readLock();
+ readLock.lock();
+ try {
+ IndexReader indexReader = getIndexReader();
+ if (indexReader != null) {
+ IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+ try {
+ Query query = computeQuery(patternString);
+ TopDocs results = indexSearcher.search(query, resultsLimit);
+ for (ScoreDoc scoreDoc : results.scoreDocs) {
+ Document document = indexReader.document(scoreDoc.doc);
+ String taskIdentifier = document.get(IndexField.IDENTIFIER.fieldName());
+ AbstractTask task = taskList.getTask(taskIdentifier);
+ if (task != null) {
+ collector.collect(task);
+ }
+ }
+ } catch (IOException e) {
+ StatusHandler.fail(new Status(IStatus.ERROR, TasksIndexCore.BUNDLE_ID,
+ "Unexpected failure within task list index", e)); //$NON-NLS-1$
+ } finally {
+ try {
+ indexSearcher.close();
+ } catch (IOException e) {
+ e.printStackTrace();
}
}
- } catch (IOException e) {
- StatusHandler.fail(new Status(IStatus.ERROR, TasksIndexCore.BUNDLE_ID,
- "Unexpected failure within task list index", e)); //$NON-NLS-1$
- } finally {
- try {
- indexSearcher.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
}
+ } finally {
+ readLock.unlock();
}
}
@@ -468,22 +486,26 @@
// ignore
}
- synchronized (this) {
- if (indexReader != null) {
- synchronized (indexReader) {
+ Lock writeLock = indexReaderLock.writeLock();
+ writeLock.lock();
+ try {
+ synchronized (this) {
+ if (indexReader != null) {
try {
indexReader.close();
} catch (IOException e) {
e.printStackTrace();
}
+ indexReader = null;
}
- indexReader = null;
}
- }
- try {
- directory.close();
- } catch (IOException e) {
- e.printStackTrace();
+ try {
+ directory.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } finally {
+ writeLock.unlock();
}
}
@@ -802,13 +824,17 @@
}
monitor.worked(WORK_PER_SEGMENT);
}
- synchronized (TaskListIndex.this) {
- if (indexReader != null) {
- synchronized (indexReader) {
+ Lock writeLock = indexReaderLock.writeLock();
+ writeLock.lock();
+ try {
+ synchronized (TaskListIndex.this) {
+ if (indexReader != null) {
indexReader.close();
+ indexReader = null;
}
- indexReader = null;
}
+ } finally {
+ writeLock.unlock();
}
} catch (CoreException e) {
throw e;
diff --git a/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/TaskListIndexTest.java b/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/TaskListIndexTest.java
index ee82c2f..6ae7592 100644
--- a/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/TaskListIndexTest.java
+++ b/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/TaskListIndexTest.java
@@ -11,7 +11,7 @@
package org.eclipse.mylyn.internal.tasks.index.tests;
-import static org.junit.Assert.assertEquals;
+import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -20,12 +20,20 @@
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.logging.Logger;
+import junit.framework.Assert;
+
import org.eclipse.core.runtime.CoreException;
import org.eclipse.mylyn.commons.core.DelegatingProgressMonitor;
import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
@@ -318,4 +326,56 @@
// should now match
assertTrue(index.matches(repositoryTask, content));
}
+
+ @Test
+ public void testMultithreadedAccessOnFind() throws CoreException, InterruptedException, ExecutionException {
+ setupIndex();
+
+ final ITask repositoryTask = context.createRepositoryTask();
+
+ index.waitUntilIdle();
+ index.setDefaultField(IndexField.CONTENT);
+
+ final int nThreads = 10;
+ final int[] concurrencyLevel = new int[1];
+ ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
+ try {
+ Collection<Callable<Object>> tasks = new HashSet<Callable<Object>>();
+ for (int x = 0; x < nThreads; ++x) {
+ tasks.add(new Callable<Object>() {
+
+ public Object call() throws Exception {
+ final int[] hitCount = new int[1];
+ index.find(repositoryTask.getSummary(), new TaskCollector() {
+
+ @Override
+ public void collect(ITask task) {
+ synchronized (concurrencyLevel) {
+ ++concurrencyLevel[0];
+ if (concurrencyLevel[0] < nThreads) {
+ try {
+ concurrencyLevel.wait(5000L);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } else {
+ concurrencyLevel.notifyAll();
+ }
+ }
+ ++hitCount[0];
+ }
+ }, 100);
+ return hitCount[0] == 1;
+ }
+ });
+ }
+ List<Future<Object>> futures = executorService.invokeAll(tasks);
+ for (Future<Object> future : futures) {
+ assertEquals(Boolean.TRUE, future.get());
+ }
+ Assert.assertEquals(nThreads, concurrencyLevel[0]);
+ } finally {
+ executorService.shutdownNow();
+ }
+ }
}