/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* A {@link ScheduledExecutorService} that has 2 thread pools, 1 for the normal execute and 1 for scheduled execution.
* This is because the {@link ScheduledThreadPoolExecutor} is a fixed size thread pool. And will not grow.
* This class does have options to let the normal calls to {@link ExecutorService#execute(Runnable)} grow the thread pool if all
* the threads are busy to a maximum of the executorMaximumPoolSize. When using the {@link ScheduledExecutorService#schedule(Callable, long, TimeUnit)}
* or other schedule calls it will map on the fixed size thread pool specified by the scheduledExecutorSize parameter.
*
* @author jcompagner
*/
public class ServoyScheduledExecutor extends ThreadPoolExecutor implements ScheduledExecutorService, ITaskExecuter
{
private final Runnable NOTHING = new Runnable()
{
public void run()
{
try
{
// sleep for a while so that if there are more then one of these
// runnables inserted it doesn't get handled by 1 worker.
Thread.sleep(10);
}
catch (InterruptedException e)
{
}
}
};
private volatile ScheduledThreadPoolExecutor scheduledService;
private final int scheduledExecutorSize;
private final int executorSize;
public ServoyScheduledExecutor()
{
this(3, 30, 3);
}
/**
* @param executorSize The core size normal executor
* @param executorMaximumPoolSize The maximum pool size that the normal executor can grow to.
* @param scheduledExecutorSize The (fixed) core size of the scheduled executor
*/
public ServoyScheduledExecutor(int executorSize, int executorMaximumPoolSize, int scheduledExecutorSize)
{
super(executorSize, executorMaximumPoolSize, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
this.executorSize = executorSize;
this.scheduledExecutorSize = scheduledExecutorSize;
}
/**
* @param scheduledExecutorSize
*/
private synchronized ScheduledExecutorService getScheduledExecutorService()
{
if (scheduledService == null)
{
scheduledService = new ScheduledThreadPoolExecutor(scheduledExecutorSize, getThreadFactory(), getRejectedExecutionHandler())
{
@Override
protected void beforeExecute(Thread t, Runnable r)
{
ServoyScheduledExecutor.this.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t)
{
ServoyScheduledExecutor.this.afterExecute(r, t);
}
};
}
return scheduledService;
}
// a thread local that will be set on threads of this scheduler, to check recusion in execute
private static final ThreadLocal<Boolean> schedulerThreadsCheck = new ThreadLocal<Boolean>();
@Override
protected void beforeExecute(Thread t, Runnable r)
{
schedulerThreadsCheck.set(Boolean.TRUE);
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t)
{
schedulerThreadsCheck.remove();
super.afterExecute(r, t);
}
/**
* @see java.util.concurrent.ScheduledExecutorService#schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit)
*/
public ScheduledFuture< ? > schedule(Runnable command, long delay, TimeUnit unit)
{
return getScheduledExecutorService().schedule(command, delay, unit);
}
/**
* @see java.util.concurrent.ScheduledExecutorService#schedule(java.util.concurrent.Callable, long, java.util.concurrent.TimeUnit)
*/
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
{
return getScheduledExecutorService().schedule(callable, delay, unit);
}
/**
* @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)
*/
public ScheduledFuture< ? > scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
{
return getScheduledExecutorService().scheduleAtFixedRate(command, initialDelay, period, unit);
}
/**
* @see java.util.concurrent.ScheduledExecutorService#scheduleWithFixedDelay(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)
*/
public ScheduledFuture< ? > scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
{
return getScheduledExecutorService().scheduleWithFixedDelay(command, initialDelay, delay, unit);
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#execute(java.lang.Runnable)
*/
@Override
public void execute(Runnable command)
{
// hack around the fact that the java 5 Executor will not grow and shrink like we want it to.
// java 6 has support for it to also be able to time out core pool threads.
// make it also always one bigger if the caller is a scheduler thread it self and the active count => current core size.
synchronized (this)
{
int activeCount = super.getActiveCount();
int coreSize = super.getCorePoolSize();
if (activeCount >= coreSize && (super.getMaximumPoolSize() > coreSize || schedulerThreadsCheck.get() != null))
{
setCorePoolSize(coreSize + 1);
}
else if (coreSize > executorSize && (activeCount + 1) < coreSize)
{
int newCoreSize = Math.max(executorSize, activeCount + 1);
setCorePoolSize(newCoreSize);
while (newCoreSize++ < coreSize)
{
super.execute(NOTHING);
}
}
}
super.execute(command);
}
/*
* (non-Javadoc)
*
* @see com.servoy.j2db.util.ITaskExecuter#addTask(java.lang.Runnable)
*/
@Deprecated
public void addTask(Runnable task) throws IllegalArgumentException
{
execute(task);
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#isTerminated()
*/
@Override
public boolean isTerminated()
{
return (scheduledService == null || scheduledService.isTerminated()) && super.isTerminated();
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#shutdown()
*/
@Override
public void shutdown()
{
try
{
if (scheduledService != null) scheduledService.shutdown();
}
finally
{
super.shutdown();
}
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#shutdownNow()
*/
@Override
public List<Runnable> shutdownNow()
{
ArrayList<Runnable> lst = new ArrayList<Runnable>();
try
{
if (scheduledService != null) lst.addAll(scheduledService.shutdownNow());
}
finally
{
lst.addAll(super.shutdownNow());
}
return lst;
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#remove(java.lang.Runnable)
*/
@Override
public boolean remove(Runnable task)
{
if (!super.remove(task))
{
if (scheduledService != null) return scheduledService.remove(task);
}
return false;
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#awaitTermination(long, java.util.concurrent.TimeUnit)
*/
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
{
long time = System.currentTimeMillis();
boolean b = true;
if (scheduledService != null) b = scheduledService.awaitTermination(timeout, unit);
if (b)
{
long elapsed = System.currentTimeMillis() - time;
elapsed = unit.convert(elapsed, TimeUnit.MILLISECONDS);
b = super.awaitTermination((timeout - elapsed), unit);
}
return b;
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#setThreadFactory(java.util.concurrent.ThreadFactory)
*/
@Override
public void setThreadFactory(ThreadFactory threadFactory)
{
super.setThreadFactory(threadFactory);
if (scheduledService != null) scheduledService.setThreadFactory(threadFactory);
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#setRejectedExecutionHandler(java.util.concurrent.RejectedExecutionHandler)
*/
@Override
public void setRejectedExecutionHandler(RejectedExecutionHandler handler)
{
super.setRejectedExecutionHandler(handler);
if (scheduledService != null) scheduledService.setRejectedExecutionHandler(handler);
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#getActiveCount()
*/
@Override
public int getActiveCount()
{
return (scheduledService == null ? 0 : scheduledService.getActiveCount()) + super.getActiveCount();
}
/**
* @see java.util.concurrent.ThreadPoolExecutor#getCompletedTaskCount()
*/
@Override
public long getCompletedTaskCount()
{
return (scheduledService == null ? 0 : scheduledService.getCompletedTaskCount()) + super.getCompletedTaskCount();
}
}