package org.jbpm.job.executor; import java.io.Serializable; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jbpm.JbpmConfiguration; import org.jbpm.job.Job; import edu.emory.mathcs.backport.java.util.concurrent.locks.Condition; import edu.emory.mathcs.backport.java.util.concurrent.locks.Lock; import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock; public class JobExecutor implements Serializable { private static final long serialVersionUID = 1L; protected JbpmConfiguration jbpmConfiguration; protected String name; protected int nbrOfThreads; protected int idleInterval; protected int maxIdleInterval; private int retryInterval; /** @deprecated property has no effect */ protected int historyMaxSize; protected int maxLockTime; protected int lockMonitorInterval; /** @deprecated property has no effect */ protected int lockBufferTime; private ThreadGroup threadGroup; private int waitingExecutorCount ; private boolean waitingDispatcher ; private boolean dispatcherActive ; private Lock waitingExecutorLock = new ReentrantLock() ; private Condition waitingExecutorCondition = waitingExecutorLock.newCondition() ; private Condition waitingDispatcherCondition = waitingExecutorLock.newCondition() ; private LinkedList dispatchedJobs = new LinkedList(); /** @deprecated call {@link #getThreads()} instead */ protected Map threads; /** @deprecated call {@link #getThreads()} instead */ protected LockMonitorThread lockMonitorThread; protected Map monitoredJobIds = new Hashtable(); protected boolean isStarted; /** @deprecated this field was just an aid for generating thread names */ protected static String hostName; public synchronized void start() { if (!isStarted) { log.info("starting " + name); activateDispatcher() ; // create thread group threadGroup = new ThreadGroup(name) { public void uncaughtException(Thread thread, Throwable throwable) { if (thread instanceof JobExecutorThread) { startThread(thread.getName()); } else if (thread instanceof DispatcherThread) { startDispatcherThread(); } else if (thread instanceof LockMonitorThread) { startLockMonitorThread(); } super.uncaughtException(thread, throwable); } }; // start executor threads for (int i = 1; i <= nbrOfThreads; i++) { startThread(getThreadName(i)); } // start control threads startDispatcherThread(); startLockMonitorThread(); isStarted = true; } else if (log.isDebugEnabled()) { log.debug("ignoring start: " + name + " already started"); } } /** * tells all threads in this job executor to stop. Threads may be in the middle of processing * a job and they will finish that first. Use {@link #stopAndJoin()} in case you want a method * that blocks until all the threads are actually finished. * * @return a list of the stopped threads. In case no threads were stopped an empty list will * be returned. */ public synchronized List stop() { if (!isStarted) { if (log.isDebugEnabled()) log.debug("ignoring stop, " + name + " not started"); return Collections.EMPTY_LIST; } log.info("stopping " + name); isStarted = false; // fetch active threads Thread[] activeThreads = new Thread[nbrOfThreads + 2]; int activeCount = threadGroup.enumerate(activeThreads, false); // deactivate threads List deactivatedThreads = new ArrayList(activeCount); for (int i = 0; i < activeCount; i++) { Thread thread = activeThreads[i]; if (thread instanceof Deactivable) { Deactivable deactivable = (Deactivable) thread; deactivable.deactivate(); deactivatedThreads.add(thread); } } deactivateDispatcher() ; // return deactivated threads return deactivatedThreads; } public void stopAndJoin() throws InterruptedException { // deactivate threads List threads = stop(); // join deactivated threads for (Iterator i = threads.iterator(); i.hasNext();) { Thread thread = (Thread) i.next(); thread.join(); } } public void ensureThreadsAreActive() { Map threads = getThreads(); // check executor threads for (int i = 1; i <= nbrOfThreads; i++) { String threadName = getThreadName(i); if (!threads.containsKey(threadName)) { startThread(threadName); } } // check control threads if (!threads.containsKey(getDispatcherThreadName())) { startDispatcherThread(); } if (!threads.containsKey(getLockMonitorThreadName())) { startLockMonitorThread(); } } ThreadGroup getThreadGroup() { return threadGroup; } private String getThreadName(int index) { return name + '@' + getHostAddress() + ":Executor-" + index; } /** @deprecated no longer invoked */ protected void startThread() { startThread(getNextThreadName()); } protected void startThread(String threadName) { Thread thread = createThread(threadName); if (log.isDebugEnabled()) log.debug("starting " + threadName); thread.start(); } protected Thread createThread(String threadName) { return new JobExecutorThread(threadName, this); } /** @deprecated no longer invoked */ protected String getNextThreadName() { return getThreadName(threadGroup.activeCount() + 1); } /** @deprecated no longer invoked */ protected String getLastThreadName() { Map threads = getThreads(); for (int i = nbrOfThreads; i > 0; i--) { String threadName = getThreadName(i); if (threads.containsKey(threadName)) return threadName; } return null; } /** @deprecated no longer invoked */ protected synchronized Thread stopThread() { Map threads = getThreads(); for (int i = nbrOfThreads; i > 0; i--) { String threadName = getThreadName(i); JobExecutorThread executorThread = (JobExecutorThread) threads.get(threadName); if (executorThread != null) { executorThread.deactivate(); return executorThread; } } return null; } private String getDispatcherThreadName() { return name + '@' + getHostAddress() + ':' + DispatcherThread.DEFAULT_NAME; } void startDispatcherThread() { String threadName = getDispatcherThreadName(); Thread dispatcherThread = new DispatcherThread(threadName, this); if (log.isDebugEnabled()) log.debug("starting " + threadName); dispatcherThread.start(); } private String getLockMonitorThreadName() { return name + '@' + getHostAddress() + ':' + LockMonitorThread.DEFAULT_NAME; } void startLockMonitorThread() { String threadName = getLockMonitorThreadName(); Thread lockMonitorThread = new LockMonitorThread(threadName, this); if (log.isDebugEnabled()) log.debug("starting " + threadName); lockMonitorThread.start(); } private static String getHostAddress() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { return "127.0.0.1"; } } public Collection getMonitoredJobIds() { return monitoredJobIds.values(); } public void addMonitoredJobId(String threadName, long jobId) { monitoredJobIds.put(threadName, new Long(jobId)); } public void removeMonitoredJobId(String threadName) { monitoredJobIds.remove(threadName); } /** * Method has no effect. * * @deprecated call {@link #addMonitoredJobId(String, long)} or * {@link #removeMonitoredJobId(String)} to manipulate the set of monitored jobs */ public void setMonitoredJobIds(Map monitoredJobIds) { } /** @deprecated property has no effect */ public int getHistoryMaxSize() { return historyMaxSize; } /** @deprecated property has no effect */ public void setHistoryMaxSize(int historyMaxSize) { this.historyMaxSize = historyMaxSize; } public int getIdleInterval() { return idleInterval; } public void setIdleInterval(int idleInterval) { if (idleInterval <= 0) { throw new IllegalArgumentException("idle interval must be positive"); } this.idleInterval = idleInterval; } /** * Tells whether this job executor has been {@linkplain #start() started}. */ public boolean isStarted() { return isStarted; } /** * This method has no effect. * * @deprecated call {@link #start()} or {@link #stop()} to control this job executor. */ public void setStarted(boolean isStarted) { } public JbpmConfiguration getJbpmConfiguration() { return jbpmConfiguration; } public void setJbpmConfiguration(JbpmConfiguration jbpmConfiguration) { this.jbpmConfiguration = jbpmConfiguration; } public int getRetryInterval() { return retryInterval; } public void setRetryInterval(int retryInterval) { if (retryInterval <= 0) { throw new IllegalArgumentException("retry interval must be positive"); } this.retryInterval = retryInterval; } public int getMaxIdleInterval() { return maxIdleInterval; } public void setMaxIdleInterval(int maxIdleInterval) { if (maxIdleInterval <= 0) { throw new IllegalArgumentException("max idle interval must be positive"); } this.maxIdleInterval = maxIdleInterval; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * @deprecated replaced by {@link #getNbrOfThreads()} */ public int getSize() { return nbrOfThreads; } /** * @deprecated replaced by {@link #setNbrOfThreads(int)} */ public void setSize(int nbrOfThreads) { this.nbrOfThreads = nbrOfThreads; } public Map getThreads() { // fetch active threads Thread[] activeThreads = new Thread[nbrOfThreads + 2]; int activeCount = threadGroup.enumerate(activeThreads, false); // map threads by name Map threadMap = new HashMap(activeCount); for (int i = 0; i < activeCount; i++) { Thread thread = activeThreads[i]; threadMap.put(thread.getName(), thread); } return threadMap; } /** * This method has no effect. * * @deprecated this job executor manages its own thread pool */ public void setThreads(Map threads) { } public int getMaxLockTime() { return maxLockTime; } public void setMaxLockTime(int maxLockTime) { if (maxLockTime <= 0) { throw new IllegalArgumentException("max lock time must be positive"); } this.maxLockTime = maxLockTime; } /** @deprecated property has no effect */ public int getLockBufferTime() { return lockBufferTime; } /** @deprecated property has no effect */ public void setLockBufferTime(int lockBufferTime) { this.lockBufferTime = lockBufferTime; } public int getLockMonitorInterval() { return lockMonitorInterval; } public void setLockMonitorInterval(int lockMonitorInterval) { if (lockMonitorInterval <= 0) { throw new IllegalArgumentException("lock monitor interval must be positive"); } this.lockMonitorInterval = lockMonitorInterval; } public int getNbrOfThreads() { return nbrOfThreads; } public void setNbrOfThreads(int nbrOfThreads) { if (nbrOfThreads <= 0) { throw new IllegalArgumentException("number of threads must be positive"); } this.nbrOfThreads = nbrOfThreads; } private boolean hasFreeExecutor() { waitingExecutorLock.lock() ; try { return (waitingExecutorCount > dispatchedJobs.size()) ; } finally { waitingExecutorLock.unlock() ; } } // return false when interrupted boolean waitForFreeExecutorThread() { waitingExecutorLock.lock() ; try { waitingDispatcher = true ; if (dispatcherActive) { if (hasFreeExecutor()) { return true ; } else { waitingDispatcherCondition.await() ; return hasFreeExecutor() ; } } } catch (final InterruptedException ie) { } finally { waitingDispatcher = false ; waitingExecutorLock.unlock() ; } return false ; } // return null when interrupted Job getJob() { waitingExecutorLock.lock() ; try { waitingExecutorCount++ ; if (dispatcherActive) { if (waitingDispatcher && hasFreeExecutor()) { waitingDispatcherCondition.signal() ; } if (dispatchedJobs.isEmpty()) { waitingExecutorCondition.await() ; } if (dispatchedJobs.size() > 0) { return (Job)dispatchedJobs.remove() ; } } } catch (final InterruptedException ie) { } finally { waitingExecutorCount-- ; waitingExecutorLock.unlock() ; } return null ; } boolean submitJob(final Job job) { waitingExecutorLock.lock() ; try { if (hasFreeExecutor()) { dispatchedJobs.add(job) ; waitingExecutorCondition.signal() ; return true ; } } finally { waitingExecutorLock.unlock() ; } return false ; } private void activateDispatcher() { waitingExecutorLock.lock() ; dispatcherActive = true ; waitingExecutorLock.unlock() ; } private void deactivateDispatcher() { waitingExecutorLock.lock() ; try { dispatcherActive = false ; waitingDispatcherCondition.signal() ; waitingExecutorCondition.signalAll() ; } finally { waitingExecutorLock.unlock() ; } } private static Log log = LogFactory.getLog(JobExecutor.class); }