blob: b8c45a4815fc42d6c1ba3b5805a1df8f10d1a96f [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2003, 2004 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 - Initial API and implementation
**********************************************************************/
package org.eclipse.core.internal.jobs;
import org.eclipse.core.internal.runtime.Assert;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.osgi.service.prefs.Preferences;
/**
* Maintains a pool of worker threads. Threads are constructed lazily as
* required, and are eventually discarded if not in use for awhile. This class
* maintains the thread creation/destruction policies for the job manager.
*
* Implementation note: all the data structures of this class are protected
* by the instance's object monitor. To avoid deadlock with third party code,
* this lock is never held when calling methods outside this class that may in
* turn use locks.
*/
class WorkerPool {
/**
* Threads not used by their best before timestamp are destroyed.
*/
private static final int BEST_BEFORE = 60000;
private static final int DEFAULT_MAX_THREADS = 25;
/**
* There will always be at least MIN_THREADS workers in the pool.
*/
private static final int MIN_THREADS = 1;
/**
* Use the busy thread count to avoid starting new threads when a living
* thread is just doing house cleaning (notifying listeners, etc).
*/
private int busyThreads = 0;
private JobManager manager;
private int MAX_THREADS = DEFAULT_MAX_THREADS;
/**
* The number of workers in the threads array
*/
private int numThreads = 0;
/**
* The number of threads that are currently sleeping
*/
private int sleepingThreads = 0;
/**
* The living set of workers in this pool.
*/
private Worker[] threads = new Worker[10];
protected WorkerPool(JobManager manager) {
this.manager = manager;
computeMaxThreads();
Preferences node = Platform.getPreferencesService().getRootNode().node(InstanceScope.SCOPE).node(IPlatform.PI_RUNTIME);
IEclipsePreferences eclipseNode = (IEclipsePreferences) node;
eclipseNode.addPreferenceChangeListener(new IEclipsePreferences.IPreferenceChangeListener() {
public void preferenceChange(PreferenceChangeEvent event) {
if (event.getKey().equalsIgnoreCase(IPlatform.PREF_PLATFORM_PERFORMANCE))
computeMaxThreads();
}
});
}
/**
* Adds a worker to the list of workers.
*/
private synchronized void add(Worker worker) {
int size = threads.length;
if (numThreads + 1 > size) {
Worker[] newThreads = new Worker[2 * size];
System.arraycopy(threads, 0, newThreads, 0, size);
threads = newThreads;
}
threads[numThreads++] = worker;
}
/**
* Computes the maximum number of threads based on the machine speed
* preference.
*/
protected void computeMaxThreads() {
int speed = Platform.getPreferencesService().getRootNode().node(InstanceScope.SCOPE).node(IPlatform.PI_RUNTIME).getInt(IPlatform.PREF_PLATFORM_PERFORMANCE, 3);
switch (speed) {
case 1 :
MAX_THREADS = DEFAULT_MAX_THREADS / 5;
break;
case 2 :
MAX_THREADS = DEFAULT_MAX_THREADS / 2;
break;
case 4 :
MAX_THREADS = DEFAULT_MAX_THREADS * 2;
break;
case 5 :
MAX_THREADS = DEFAULT_MAX_THREADS * 5;
break;
case 3 :
default :
MAX_THREADS = DEFAULT_MAX_THREADS;
}
}
private synchronized void decrementBusyThreads() {
//impossible to have less than zero busy threads
if (--busyThreads < 0) {
if (JobManager.DEBUG)
Assert.isTrue(false, Integer.toString(busyThreads));
busyThreads = 0;
}
}
/**
* Signals the end of a job. Note that this method can be called under
* OutOfMemoryError conditions and thus must be paranoid about allocating objects.
*/
protected void endJob(InternalJob job, IStatus result) {
decrementBusyThreads();
//need to end rule in graph before ending job so that 2 threads
//do not become the owners of the same rule in the graph
if ((job.getRule() != null) && !(job instanceof ImplicitJobs.ThreadJob)) {
//remove any locks this thread may be owning on that rule
manager.getLockManager().removeLockCompletely(Thread.currentThread(), job.getRule());
}
manager.endJob(job, result, true);
}
/**
* Signals the death of a worker thread. Note that this method can be called under
* OutOfMemoryError conditions and thus must be paranoid about allocating objects.
*/
protected synchronized void endWorker(Worker worker) {
if (remove(worker) && JobManager.DEBUG)
JobManager.debug("worker removed from pool: " + worker); //$NON-NLS-1$
}
private synchronized void incrementBusyThreads() {
//impossible to have more busy threads than there are threads
if (++busyThreads > numThreads) {
if (JobManager.DEBUG)
Assert.isTrue(false, Integer.toString(busyThreads) + ',' + numThreads);
busyThreads = numThreads;
}
}
/**
* Notfication that a job has been added to the queue. Wake a worker,
* creating a new worker if necessary. The provided job may be null.
*/
protected synchronized void jobQueued(InternalJob job) {
//if there is a sleeping thread, wake it up
if (sleepingThreads > 0) {
if (JobManager.DEBUG)
JobManager.debug("notifiying a worker"); //$NON-NLS-1$
notify();
return;
}
int threadCount = numThreads;
//create a thread if all threads are busy and we're under the max size
//if the job is high priority, we start a thread no matter what
if (busyThreads >= threadCount) {
Worker worker = new Worker(this);
add(worker);
if (JobManager.DEBUG)
JobManager.debug("worker added to pool: " + worker); //$NON-NLS-1$
worker.start();
return;
} else if (JobManager.DEBUG && threadCount >= MAX_THREADS) {
//this is a potential problem, but we don't want to flood the user with these messages
JobManager.debug("Stopped allocating worker threads. Thread count: " + threadCount); //$NON-NLS-1$
}
}
/**
* Remove a worker thread from our list.
* @return true if a worker was removed, and false otherwise.
*/
private boolean remove(Worker worker) {
for (int i = 0; i < threads.length; i++) {
if (threads[i] == worker) {
System.arraycopy(threads, i + 1, threads, i, numThreads - i - 1);
threads[--numThreads] = null;
return true;
}
}
return false;
}
protected synchronized void shutdown() {
notifyAll();
}
/**
* Sleep for the given duration or until woken.
*/
private synchronized void sleep(long duration) {
sleepingThreads++;
if (JobManager.DEBUG)
JobManager.debug("worker sleeping for: " + duration + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
try {
wait(duration);
} catch (InterruptedException e) {
if (JobManager.DEBUG)
JobManager.debug("worker interrupted while waiting... :-|"); //$NON-NLS-1$
} finally {
sleepingThreads--;
}
}
/**
* Returns a new job to run. Returns null if the thread should die.
*/
protected InternalJob startJob(Worker worker) {
//if we're above capacity, kill the thread
synchronized (this) {
if (!manager.isActive()) {
//must remove the worker immediately to prevent all threads from expiring
endWorker(worker);
return null;
}
}
Job job = manager.startJob();
//spin until a job is found or until we have been idle for too long
long idleStart = System.currentTimeMillis();
while (manager.isActive() && job == null) {
long hint = manager.sleepHint();
if (hint > 0)
sleep(Math.min(hint, BEST_BEFORE));
job = manager.startJob();
//if we were already idle, and there are still no new jobs, then
// the thread can expire
synchronized (this) {
if (job == null && (System.currentTimeMillis() - idleStart > BEST_BEFORE) && (numThreads - busyThreads) > MIN_THREADS) {
//must remove the worker immediately to prevent all threads from expiring
endWorker(worker);
return null;
}
}
}
if (job != null) {
incrementBusyThreads();
//if this job has a rule, then we are essentially acquiring a lock
if ((job.getRule() != null) && !(job instanceof ImplicitJobs.ThreadJob)) {
//don't need to reaquire locks because it was not recorded in the graph
//that this thread waited to get this rule
manager.getLockManager().addLockThread(Thread.currentThread(), job.getRule());
}
//see if we need to wake another worker
if (manager.sleepHint() <= 0)
jobQueued(null);
}
return job;
}
protected synchronized void startup() {
computeMaxThreads();
}
}