blob: 1843eff0307fa9f6e5c25ae5d7a918f61ce40ad4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.search.processing;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.internal.core.search.Util;
public abstract class JobManager implements Runnable {
/* queue of jobs to execute */
protected IJob[] awaitingJobs = new IJob[10];
protected int jobStart = 0;
protected int jobEnd = -1;
protected boolean executing = false;
/* background processing */
protected Thread processingThread;
/* flag indicating whether job execution is enabled or not */
private boolean enabled = true;
public static boolean VERBOSE = false;
/* flag indicating that the activation has completed */
public boolean activated = false;
private int awaitingClients = 0;
public static void verbose(String log) {
System.out.println("(" + Thread.currentThread() + ") " + log); //$NON-NLS-1$//$NON-NLS-2$
}
/**
* Invoked exactly once, in background, before starting processing any job
*/
public void activateProcessing() {
this.activated = true;
}
/**
* Answer the amount of awaiting jobs.
*/
public synchronized int awaitingJobsCount() {
// pretend busy in case concurrent job attempts performing before activated
if (!activated)
return 1;
return jobEnd - jobStart + 1;
}
/**
* Answers the first job in the queue, or null if there is no job available
* Until the job has completed, the job manager will keep answering the same job.
*/
public synchronized IJob currentJob() {
if (!enabled)
return null;
if (jobStart <= jobEnd) {
return awaitingJobs[jobStart];
}
return null;
}
public void disable() {
enabled = false;
if (VERBOSE)
JobManager.verbose("DISABLING background indexing"); //$NON-NLS-1$
}
/**
* Remove the index from cache for a given project.
* Passing null as a job family discards them all.
*/
public void discardJobs(String jobFamily) {
if (VERBOSE)
JobManager.verbose("DISCARD background job family - " + jobFamily); //$NON-NLS-1$
boolean wasEnabled = isEnabled();
try {
IJob currentJob;
// cancel current job if it belongs to the given family
synchronized(this){
currentJob = this.currentJob();
disable();
}
if (currentJob != null
&& (jobFamily == null || currentJob.belongsTo(jobFamily))) {
currentJob.cancel();
// wait until current active job has finished
while (processingThread != null && executing){
try {
if (VERBOSE)
JobManager.verbose("-> waiting end of current background job - " + currentJob); //$NON-NLS-1$ //$NON-NLS-2$
Thread.sleep(50);
} catch(InterruptedException e){
}
}
}
// flush and compact awaiting jobs
int loc = -1;
synchronized(this) {
for (int i = jobStart; i <= jobEnd; i++) {
currentJob = awaitingJobs[i];
awaitingJobs[i] = null;
if (!(jobFamily == null
|| currentJob.belongsTo(jobFamily))) { // copy down, compacting
awaitingJobs[++loc] = currentJob;
} else {
if (VERBOSE)
JobManager.verbose("-> discarding background job - " + currentJob); //$NON-NLS-1$
currentJob.cancel();
}
}
jobStart = 0;
jobEnd = loc;
}
} finally {
if (wasEnabled)
enable();
}
if (VERBOSE)
JobManager.verbose("DISCARD DONE with background job family - " + jobFamily); //$NON-NLS-1$
}
public synchronized void enable() {
enabled = true;
if (VERBOSE)
JobManager.verbose("ENABLING background indexing"); //$NON-NLS-1$
this.notifyAll(); // wake up the background thread if it is waiting (context must be synchronized)
}
public boolean isEnabled() {
return enabled;
}
/**
* Advance to the next available job, once the current one has been completed.
* Note: clients awaiting until the job count is zero are still waiting at this point.
*/
protected synchronized void moveToNextJob() {
//if (!enabled) return;
if (jobStart <= jobEnd) {
awaitingJobs[jobStart++] = null;
if (jobStart > jobEnd) {
jobStart = 0;
jobEnd = -1;
}
}
}
/**
* When idle, give chance to do something
*/
protected void notifyIdle(long idlingTime) {
}
/**
* This API is allowing to run one job in concurrence with background processing.
* Indeed since other jobs are performed in background, resource sharing might be
* an issue.Therefore, this functionality allows a given job to be run without
* colliding with background ones.
* Note: multiple thread might attempt to perform concurrent jobs at the same time,
* and should synchronize (it is deliberately left to clients to decide whether
* concurrent jobs might interfere or not. In general, multiple read jobs are ok).
*
* Waiting policy can be:
* IJobConstants.ForceImmediateSearch
* IJobConstants.CancelIfNotReadyToSearch
* IJobConstants.WaitUntilReadyToSearch
*
*/
public boolean performConcurrentJob(
IJob searchJob,
int waitingPolicy,
IProgressMonitor progress) {
if (VERBOSE)
JobManager.verbose("STARTING concurrent job - " + searchJob); //$NON-NLS-1$
if (!searchJob.isReadyToRun()) {
if (VERBOSE)
JobManager.verbose("ABORTED concurrent job - " + searchJob); //$NON-NLS-1$
return IJob.FAILED;
}
int concurrentJobWork = 100;
if (progress != null)
progress.beginTask("", concurrentJobWork); //$NON-NLS-1$
boolean status = IJob.FAILED;
if (awaitingJobsCount() > 0) {
switch (waitingPolicy) {
case IJob.ForceImmediate :
if (VERBOSE)
JobManager.verbose("-> NOT READY - forcing immediate - " + searchJob);//$NON-NLS-1$
boolean wasEnabled = isEnabled();
try {
disable(); // pause indexing
status = searchJob.execute(progress == null ? null : new SubProgressMonitor(progress, concurrentJobWork));
} finally {
if (wasEnabled)
enable();
}
if (VERBOSE)
JobManager.verbose("FINISHED concurrent job - " + searchJob); //$NON-NLS-1$
return status;
case IJob.CancelIfNotReady :
if (VERBOSE)
JobManager.verbose("-> NOT READY - cancelling - " + searchJob); //$NON-NLS-1$
if (progress != null) progress.setCanceled(true);
if (VERBOSE)
JobManager.verbose("CANCELED concurrent job - " + searchJob); //$NON-NLS-1$
throw new OperationCanceledException();
case IJob.WaitUntilReady :
int awaitingWork;
IJob previousJob = null;
IJob currentJob;
IProgressMonitor subProgress = null;
int totalWork = this.awaitingJobsCount();
if (progress != null && totalWork > 0) {
subProgress = new SubProgressMonitor(progress, concurrentJobWork / 2);
subProgress.beginTask("", totalWork); //$NON-NLS-1$
concurrentJobWork = concurrentJobWork / 2;
}
int originalPriority = this.processingThread.getPriority();
try {
synchronized(this) {
// use local variable to avoid potential NPE (see Bug 20435 NPE when searching java method)
Thread t = this.processingThread;
if (t != null) {
t.setPriority(Thread.currentThread().getPriority());
}
this.awaitingClients++;
}
while ((awaitingWork = awaitingJobsCount()) > 0) {
if (subProgress != null && subProgress.isCanceled())
throw new OperationCanceledException();
currentJob = currentJob();
// currentJob can be null when jobs have been added to the queue but job manager is not enabled
if (currentJob != null && currentJob != previousJob) {
if (VERBOSE)
JobManager.verbose("-> NOT READY - waiting until ready - " + searchJob);//$NON-NLS-1$
if (subProgress != null) {
subProgress.subTask(
Util.bind("manager.filesToIndex", Integer.toString(awaitingWork))); //$NON-NLS-1$
subProgress.worked(1);
}
previousJob = currentJob;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
} finally {
synchronized(this) {
this.awaitingClients--;
// use local variable to avoid potential NPE (see Bug 20435 NPE when searching java method)
Thread t = this.processingThread;
if (t != null) {
t.setPriority(originalPriority);
}
}
}
if (subProgress != null) {
subProgress.done();
}
}
}
status = searchJob.execute(progress == null ? null : new SubProgressMonitor(progress, concurrentJobWork));
if (progress != null) {
progress.done();
}
if (VERBOSE)
JobManager.verbose("FINISHED concurrent job - " + searchJob); //$NON-NLS-1$
return status;
}
public abstract String processName();
public synchronized void request(IJob job) {
if (!job.isReadyToRun()) {
if (VERBOSE)
JobManager.verbose("ABORTED request of background job - " + job); //$NON-NLS-1$
return;
}
// append the job to the list of ones to process later on
int size = awaitingJobs.length;
if (++jobEnd == size) { // when growing, relocate jobs starting at position 0
jobEnd -= jobStart;
System.arraycopy(
awaitingJobs,
jobStart,
(awaitingJobs = new IJob[size * 2]),
0,
jobEnd);
jobStart = 0;
}
awaitingJobs[jobEnd] = job;
if (VERBOSE)
JobManager.verbose("REQUEST background job - " + job); //$NON-NLS-1$
this.notifyAll(); // wake up the background thread if it is waiting
}
/**
* Flush current state
*/
public void reset() {
if (VERBOSE)
JobManager.verbose("Reset"); //$NON-NLS-1$
if (processingThread != null) {
discardJobs(null); // discard all jobs
} else {
/* initiate background processing */
processingThread = new Thread(this, this.processName());
processingThread.setDaemon(true);
// less prioritary by default, priority is raised if clients are actively waiting on it
processingThread.setPriority(Thread.NORM_PRIORITY-1);
processingThread.start();
}
}
/**
* Infinite loop performing resource indexing
*/
public void run() {
long idlingStart = -1;
activateProcessing();
try {
while (this.processingThread != null) {
try {
IJob job;
synchronized (this) {
if ((job = currentJob()) == null) {
if (idlingStart < 0)
idlingStart = System.currentTimeMillis();
notifyIdle(System.currentTimeMillis() - idlingStart);
this.wait(); // wait until a new job is posted (or reenabled:38901)
Thread.sleep(500); // delay before processing the new job, allow some time for the active thread to finish
continue;
} else {
idlingStart = -1;
}
}
if (VERBOSE) {
JobManager.verbose(awaitingJobsCount() + " awaiting jobs"); //$NON-NLS-1$
JobManager.verbose("STARTING background job - " + job); //$NON-NLS-1$
}
try {
executing = true;
/*boolean status = */job.execute(null);
//if (status == FAILED) request(job);
} finally {
executing = false;
if (VERBOSE) {
JobManager.verbose("FINISHED background job - " + job); //$NON-NLS-1$
}
moveToNextJob();
if (this.awaitingClients == 0) {
Thread.sleep(50);
}
}
} catch (InterruptedException e) { // background indexing was interrupted
}
}
} catch (RuntimeException e) {
if (this.processingThread != null) { // if not shutting down
// log exception
org.eclipse.jdt.internal.core.Util.log(e, "Background Indexer Crash Recovery"); //$NON-NLS-1$
// keep job manager alive
this.discardJobs(null);
this.processingThread = null;
this.reset(); // this will fork a new thread with no waiting jobs, some indexes will be inconsistent
}
throw e;
} catch (Error e) {
if (this.processingThread != null && !(e instanceof ThreadDeath)) {
// log exception
org.eclipse.jdt.internal.core.Util.log(e, "Background Indexer Crash Recovery"); //$NON-NLS-1$
// keep job manager alive
this.discardJobs(null);
this.processingThread = null;
this.reset(); // this will fork a new thread with no waiting jobs, some indexes will be inconsistent
}
throw e;
}
}
/**
* Stop background processing, and wait until the current job is completed before returning
*/
public void shutdown() {
disable();
discardJobs(null); // will wait until current executing job has completed
Thread thread = this.processingThread;
this.processingThread = null; // mark the job manager as shutting down so that the thread will stop by itself
try {
if (thread != null) { // see http://bugs.eclipse.org/bugs/show_bug.cgi?id=31858
synchronized (this) {
this.notifyAll(); // ensure its awake so it can be shutdown
}
thread.join();
}
} catch (InterruptedException e) {
}
}
public String toString() {
StringBuffer buffer = new StringBuffer(10);
buffer.append("Enabled:").append(this.enabled).append('\n'); //$NON-NLS-1$
int numJobs = jobEnd - jobStart + 1;
buffer.append("Jobs in queue:").append(numJobs).append('\n'); //$NON-NLS-1$
for (int i = 0; i < numJobs && i < 15; i++) {
buffer.append(i).append(" - job["+i+"]: ").append(awaitingJobs[jobStart+i]).append('\n'); //$NON-NLS-1$ //$NON-NLS-2$
}
return buffer.toString();
}
}