/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.utils; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.LinkedBlockingQueue; import nl.strohalm.cyclos.utils.access.LoggedUser; import org.apache.commons.lang.time.DateUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * An abstract implementation for a thread group of consumer threads * @author luis */ public abstract class WorkerThreads<T> { private final class WorkerThread extends Thread { private long lastUsedAt; private boolean inProcess; @Override public void run() { while (true) { // Block until something is available or this thread has been stopped final T object; try { object = queue.take(); } catch (final InterruptedException e) { // This thread has been interrupted. Leave the loop break; } inProcess = true; // Update the last used counter lastUsedAt = System.currentTimeMillis(); // Process the object LoggedUser.runAsSystem(new Callable<Void>() { @Override public Void call() throws Exception { try { process(object); } catch (final Exception e) { LOG.error("Error processing work by " + name, e); } return null; } }); inProcess = false; } } } private static final long CHECK_INTERVAL = DateUtils.MILLIS_PER_MINUTE; private static final Log LOG = LogFactory.getLog(WorkerThreads.class); private String name; private List<WorkerThread> threads; private int maxThreads; private BlockingQueue<T> queue = new LinkedBlockingQueue<T>(); private long threadIndex; private Timer cleanUpTimer; protected WorkerThreads(final String name, final int maxThreads) { this(name, maxThreads, true); } protected WorkerThreads(final String name, final int maxThreads, final boolean purgeOld) { this.name = name; this.maxThreads = maxThreads; threads = Collections.synchronizedList(new LinkedList<WorkerThread>()); if (purgeOld) { cleanUpTimer = new Timer("Clean up timer for " + name); final TimerTask task = new TimerTask() { @Override public void run() { interruptOldThreads(); } }; cleanUpTimer.scheduleAtFixedRate(task, CHECK_INTERVAL, CHECK_INTERVAL); } } /** * Enqueues the given object for processing */ public synchronized void enqueue(final T object) { if (maxThreads <= 0) { return; } queue.offer(object); final int queueSize = queue.size(); final int threadsSize = threads.size(); if (threadsSize < maxThreads && threadsSize < queueSize) { // Start another thread final WorkerThread thread = new WorkerThread(); thread.setName("#" + (threadIndex++) + " " + name); threads.add(thread); thread.start(); } } /** * Enqueues all the given objects for processing */ public void enqueueAll(final Collection<T> objects) { for (final T object : objects) { enqueue(object); } } /** * Interrupts all threads */ public synchronized void interrupt() { if (cleanUpTimer != null) { cleanUpTimer.cancel(); } for (final WorkerThread thread : threads) { thread.interrupt(); } threads.clear(); } /** * Should be implemented in order to do the actual work with the given object */ protected abstract void process(T object); private synchronized void interruptOldThreads() { final long tolerance = System.currentTimeMillis() - CHECK_INTERVAL; for (final Iterator<WorkerThread> iterator = threads.iterator(); iterator.hasNext();) { final WorkerThread thread = iterator.next(); if (thread.lastUsedAt < tolerance && !thread.inProcess) { thread.interrupt(); iterator.remove(); } } } }