/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.core.concurrency;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.python.pydev.core.CorePlugin;
import org.python.pydev.core.MathUtils;
import org.python.pydev.core.log.Log;
import org.python.pydev.shared_core.structure.Tuple;
/**
* This is a pool where we can register runnables to run -- and it'll let only X runnables run at the same time.
*
* The runnables will be run as eclipse jobs.
*/
public class RunnableAsJobsPoolThread extends Thread {
/**
*
* We cannot have more than XX jobs scheduled at any time.
*/
private final Semaphore jobsCreationSemaphore;
/**
* Semaphore to run: only let it go if there's a release() acknowledging something happened.
*/
private final Semaphore canRunSemaphore = new Semaphore(0);
/**
* List of runnables and their names to run.
*/
private final List<Tuple<Runnable, String>> runnables = new ArrayList<Tuple<Runnable, String>>();
/**
* Lock to access the runnables field.
*/
private final Object lockRunnables = new Object();
/**
* Constructor
*
* @param maxSize the maximum number of runnables that can run at the same time.
*/
public RunnableAsJobsPoolThread(int maxSize) {
jobsCreationSemaphore = new Semaphore(maxSize);
this.setDaemon(true);
this.start();
}
private final Object stopThreadsLock = new Object();
private int stopThreads = 0;
public void pushStopThreads() {
synchronized (stopThreadsLock) {
stopThreads += 1;
}
}
public void popStopThreads() {
synchronized (stopThreadsLock) {
stopThreads -= 1;
Assert.isTrue(stopThreads >= 0);
if (stopThreads == 0) {
stopThreadsLock.notifyAll();
}
}
}
/**
* We'll stay here until the end of times (or at least until the vm finishes)
*/
@Override
public void run() {
while (true) {
//until we've gotten a release we'll stay here.
canRunSemaphore.acquire();
//get the runnable to run.
Tuple<Runnable, String> execute = null;
int size;
synchronized (lockRunnables) {
size = runnables.size();
if (size > 0) {
execute = runnables.remove(0);
size--;
}
}
int local = 0;
while (true) {
synchronized (stopThreadsLock) {
local = stopThreads;
}
if (local == 0) {
break;
} else {
synchronized (stopThreadsLock) {
try {
stopThreadsLock.wait(200);
} catch (InterruptedException e) {
Log.log(e);
}
}
}
}
if (execute != null) {
//this will make certain that only X jobs are running.
jobsCreationSemaphore.acquire();
final Runnable[] runnable = new Runnable[] { execute.o1 };
String name = execute.o2;
execute = null;
if (size > 1) {
name += " (" + size + " scheduled)";
}
Job workbenchJob = new Job(name) {
@Override
public IStatus run(IProgressMonitor monitor) {
Runnable r;
try {
r = runnable[0];
if (r instanceof IRunnableWithMonitor) {
((IRunnableWithMonitor) r).setMonitor(monitor);
}
runnable[0] = null;//make sure it'll be available for garbage collection ASAP.
r.run();
} catch (RuntimeException e) {
if (CorePlugin.getDefault() != null) {
//Only log if eclipse still didn't shutdown.
Log.log(e);
}
} finally {
r = null; //make sure it'll be available for garbage collection ASAP.
jobsCreationSemaphore.release();
}
return Status.OK_STATUS;
}
};
// workbenchJob.setSystem(true);
// workbenchJob.setPriority(Job.BUILD);
workbenchJob.setPriority(Job.INTERACTIVE);
workbenchJob.schedule();
}
}
}
public void scheduleToRun(final IRunnableWithMonitor runnable, final String name) {
synchronized (lockRunnables) {
runnables.add(new Tuple<Runnable, String>(runnable, name));
}
canRunSemaphore.release();
}
/**
* Meant to be used in tests!
*/
public void waitToFinishCurrent() {
final Object lock = new Object();
IRunnableWithMonitor runnable = new IRunnableWithMonitor() {
@Override
public void run() {
synchronized (lock) {
lock.notifyAll();
}
}
@Override
public void setMonitor(IProgressMonitor monitor) {
}
};
//I.e.: we'll schedule a job to wait until all the currently scheduled jobs are run.
scheduleToRun(runnable, "Wait to run all currently scheduled jobs");
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private static RunnableAsJobsPoolThread singleton;
/**
* @return a singleton to be shared across multiple clases. Note that this class
* may still have locally created instances (so, its constructor is not private as
* is usual for singletons).
*/
public synchronized static RunnableAsJobsPoolThread getSingleton() {
if (singleton == null) {
//if a problem happens getting the number of processors (although it shouldn't happen), use 6
int maxSize = 6;
try {
int availableProcessors = Runtime.getRuntime().availableProcessors();
if (availableProcessors <= 1) {
maxSize = 3;
} else {
//note that we create more threads than processes because some are very likely to
//be disk-bound processes (but with a logarithmic function, because we don't want
//to add up too fast as the number of processors increase because of the amount of memory
//it'd consume).
//
//The progression we get with this formula is below.
//
//2: 4
//3: 6
//4: 8
//5: 10
//6: 11
//7: 13
//8: 14
//9: 16
//10: 17
//11: 18
//12: 19
//13: 21
//14: 22
//15: 23
//16: 24
//17: 25
//18: 27
//19: 28
maxSize = (int) (availableProcessors + Math.round(MathUtils.log(availableProcessors, 1.4)));
}
} catch (Throwable e) {
}
singleton = new RunnableAsJobsPoolThread(maxSize);
}
return singleton;
}
}