Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File search: Reliable and fast cancel #83

Merged
merged 1 commit into from
Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion org.eclipse.search/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.search; singleton:=true
Bundle-Version: 3.14.200.qualifier
Bundle-Version: 3.14.300.qualifier
Bundle-Activator: org.eclipse.search.internal.ui.SearchPlugin
Bundle-ActivationPolicy: lazy
Bundle-Vendor: %providerName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ protected IStatus run(IProgressMonitor inner) {
SubMonitor subMonitor = SubMonitor.convert(inner, fileBatches.size() / jobCount); // approximate
this.fileCharSequenceProvider= new FileCharSequenceProvider();
List<IFile> sameFiles;
while (((sameFiles = fileBatches.poll()) != null) && !fFatalError) {
while (((sameFiles = fileBatches.poll()) != null) && !fFatalError && !fProgressMonitor.isCanceled()) {
IStatus status = processFile(sameFiles, subMonitor.split(1));
// Only accumulate interesting status
if (!status.isOK())
Expand All @@ -190,6 +190,9 @@ protected IStatus run(IProgressMonitor inner) {
// Stop processing and return the status for the completed jobs.
}
fileCharSequenceProvider= null;
synchronized (fLock) {
fLock.notify();
}
return multiStatus;
}

Expand Down Expand Up @@ -291,17 +294,16 @@ public Map<IFile, IDocument> getDocumentsInEditors() {
private final TextSearchRequestor fCollector;
private final Pattern fSearchPattern;

private IProgressMonitor fProgressMonitor;
private volatile IProgressMonitor fProgressMonitor;

private int fNumberOfFilesToScan;
private int fNumberOfScannedFiles; // Protected by fLock
private IFile fCurrentFile; // Protected by fLock
private Object fLock= new Object();
private final Object fLock = new Object();

private final MultiStatus fStatus;
private volatile boolean fFatalError; // If true, terminates the search.

private boolean fIsLightweightAutoRefresh;
private volatile boolean fIsLightweightAutoRefresh;

public TextSearchVisitor(TextSearchRequestor collector, Pattern searchPattern) {
fCollector= collector;
Expand All @@ -317,102 +319,101 @@ public IStatus search(IFile[] files, IProgressMonitor monitor) {
if (files.length == 0) {
return fStatus;
}
fProgressMonitor= monitor == null ? new NullProgressMonitor() : monitor;
fNumberOfScannedFiles= 0;
fNumberOfFilesToScan= files.length;
fCurrentFile= null;

fProgressMonitor = monitor == null ? new NullProgressMonitor() : monitor;
synchronized (fLock) {
fNumberOfScannedFiles = 0;
fCurrentFile = null;
}
int threadsNeeded = Math.min(files.length, NUMBER_OF_LOGICAL_THREADS);
// All but 1 threads should search. 1 thread does the UI updates:
int jobCount = fCollector.canRunInParallel() && threadsNeeded > 1 ? threadsNeeded - 1 : 1;

// Seed count over 1 can cause endless waits, see bug 543629 comment 2
// TODO use seed = jobCount after the bug 543660 in JobGroup is fixed
final int seed = 1;
final JobGroup jobGroup = new TextSearchJobGroup("Text Search", jobCount, seed); //$NON-NLS-1$
long startTime= TRACING ? System.currentTimeMillis() : 0;

Job monitorUpdateJob= new Job(SearchMessages.TextSearchVisitor_progress_updating_job) {
private int fLastNumberOfScannedFiles= 0;

@Override
public IStatus run(IProgressMonitor inner) {
while (!inner.isCanceled()) {
// Propagate user cancellation to the JobGroup.
if (fProgressMonitor.isCanceled()) {
jobGroup.cancel();
break;
}

IFile file;
int numberOfScannedFiles;
synchronized (fLock) {
file= fCurrentFile;
numberOfScannedFiles= fNumberOfScannedFiles;
}
if (file != null) {
String fileName= file.getName();
Object[] args= { fileName, Integer.valueOf(numberOfScannedFiles), Integer.valueOf(fNumberOfFilesToScan)};
fProgressMonitor.subTask(Messages.format(SearchMessages.TextSearchVisitor_scanning, args));
int steps= numberOfScannedFiles - fLastNumberOfScannedFiles;
fProgressMonitor.worked(steps);
fLastNumberOfScannedFiles += steps;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return Status.OK_STATUS;
}
}
return Status.OK_STATUS;
}
};

try {
String taskName= fSearchPattern.pattern().isEmpty()
? SearchMessages.TextSearchVisitor_filesearch_task_label
: ""; //$NON-NLS-1$
fProgressMonitor.beginTask(taskName, fNumberOfFilesToScan);
monitorUpdateJob.setSystem(true);
monitorUpdateJob.schedule();
try {
fCollector.beginReporting();
if (fProgressMonitor.isCanceled()) {
throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled);
}

Map<IFile, IDocument> documentsInEditors= PlatformUI.isWorkbenchRunning() ? evalNonFileBufferDocuments() : Collections.emptyMap();

// group files with same content together:

Map<String, List<IFile>> localFilesByLocation = new LinkedHashMap<>();
Map<String, List<IFile>> remotFilesByLocation = new LinkedHashMap<>();
Map<String, List<IFile>> remoteFilesByLocation = new LinkedHashMap<>();

for (IFile file : files) {
IPath path = file.getLocation();
String key = path == null ? file.getLocationURI().toString() : path.toString();
Map<String, List<IFile>> filesByLocation = (path != null) ? localFilesByLocation
: remotFilesByLocation;
: remoteFilesByLocation;
filesByLocation.computeIfAbsent(key, k -> new ArrayList<>()).add(file);

}
localFilesByLocation.values().forEach(fileBatches::offer);
remotFilesByLocation.values().forEach(fileBatches::offer);
remoteFilesByLocation.values().forEach(fileBatches::offer);
int numberOfFilesToScan = fileBatches.size();
fProgressMonitor.beginTask(taskName, numberOfFilesToScan);

// Seed count over 1 can cause endless waits, see bug 543629
// comment 2
// TODO use seed = jobCount after the bug 543660 in JobGroup is
// fixed

final int seed = 1;
final JobGroup jobGroup = new TextSearchJobGroup("Text Search", jobCount, seed); //$NON-NLS-1$
for (int i = 0; i < jobCount; i++) {
Job job = new TextSearchJob(documentsInEditors, jobCount);
job.setJobGroup(jobGroup);
job.schedule();
}

// The monitorUpdateJob is managing progress and cancellation,
// so it is ok to pass a null monitor into the job group.
// update progress until finished or canceled:
int numberOfScannedFiles = 0;
int lastNumberOfScannedFiles = 0;
while (!fProgressMonitor.isCanceled() && !jobGroup.getActiveJobs().isEmpty()
&& numberOfScannedFiles != numberOfFilesToScan) {
IFile file;
synchronized (fLock) {
try {
// time only relevant on how often progress is
// updated, but cancel is notified immediately:
fLock.wait(100);
} catch (InterruptedException e) {
fProgressMonitor.setCanceled(true);
break;
}
file = fCurrentFile;
numberOfScannedFiles = fNumberOfScannedFiles;
}
if (file != null) {
String fileName = file.getName();
Object[] args = { fileName, Integer.valueOf(numberOfScannedFiles),
Integer.valueOf(numberOfFilesToScan) };
fProgressMonitor.subTask(Messages.format(SearchMessages.TextSearchVisitor_scanning, args));
int steps = numberOfScannedFiles - lastNumberOfScannedFiles;
fProgressMonitor.worked(steps);
lastNumberOfScannedFiles += steps;
}
}
if (fProgressMonitor.isCanceled()) {
jobGroup.cancel();
}
// no need to pass progressMonitor (which would show wrong
// progress) but null because jobGroup was already finished /
// canceled anyway:
jobGroup.join(0, null);
if (fProgressMonitor.isCanceled())
if (fProgressMonitor.isCanceled()) {
throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled);
}

fStatus.addAll(jobGroup.getResult());
return fStatus;
} catch (InterruptedException e) {
throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled);
} finally {
monitorUpdateJob.cancel();
fileBatches.clear();
}
} finally {
Expand Down Expand Up @@ -501,6 +502,7 @@ private boolean hasBinaryContent(CharSequence seq, IFile file) throws CoreExcept
}
}
} catch (IndexOutOfBoundsException e) {
// ignored
} catch (FileCharSequenceException ex) {
if (ex.getCause() instanceof CharConversionException)
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ private SearchMessages() {
public static String TextSearchPage_searchBinary_label;
public static String TextSearchVisitor_filesearch_task_label;
public static String TextSearchVisitor_patterntoocomplex0;
public static String TextSearchVisitor_progress_updating_job;
public static String TextSearchVisitor_scanning;
public static String TextSearchVisitor_error;
public static String TextSearchVisitor_canceled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,8 @@ TextSearchVisitor_error= File ''{1}'' has been skipped, problem while reading: (
TextSearchVisitor_canceled= Operation Canceled
TextSearchVisitor_unsupportedcharset=File ''{1}'' has been skipped: Unsupported encoding ''{0}''.
TextSearchVisitor_patterntoocomplex0=Search pattern is too complex. Search canceled.
TextSearchVisitor_progress_updating_job=Search progress polling
TextSearchVisitor_filesearch_task_label=Searching for files...
TextSearchVisitor_textsearch_task_label=Searching for pattern ''{0}''...
TextSearchVisitor_textsearch_task_label=Searching ''{0}''
TextSearchVisitor_illegalcharset=File ''{1}'' has been skipped: Illegal encoding ''{0}''.

SortDropDownAction_label= S&ort By
Expand Down