/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.container;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jvnet.glassfish.comms.util.LogUtil;
import com.sun.grizzly.Pipeline;
import com.sun.grizzly.PipelineFullException;
/**
*
* @author Scott Oaks
*
* Thread Pool Implementation for the Sip Container.
*/
@SuppressWarnings("unchecked")
public class SipContainerThreadPool {
private static SipContainerThreadPool _singletonInstance = new SipContainerThreadPool();
private Pipeline grizzlyThreadPool;
//private ScheduledThreadPoolExecutor myThreadPool = null;
private Logger _log = LogUtil.SIP_LOGGER.getLogger();
volatile boolean isShutdown = true;
private ConcurrentLinkedQueue<Callable> taskQueue = new ConcurrentLinkedQueue<Callable>();
private Lock taskLock = new ReentrantLock();
private Condition taskCV = taskLock.newCondition();
private int nWaiters;
private SipContainerTimerThread[] timerThreads;
private SipContainerThreadPoolThread[] _threads;
private TimerQueue[] timerQueues;
private int curTimerQueue = 0;
private SipContainerThreadPool() {
initTimerQueues();
}
private void addTask(Callable c) {
taskLock.lock();
try {
taskQueue.offer(c);
if (nWaiters > 0) {
taskCV.signal();
}
} finally {
taskLock.unlock();
}
}
private Callable getTask() {
taskLock.lock();
try {
Callable task = taskQueue.poll();
while ((task == null) && !isShutdown) {
nWaiters++;
try {
taskCV.await();
} catch (InterruptedException ie) {
}
nWaiters--;
task = taskQueue.poll();
}
return task;
} finally {
taskLock.unlock();
}
}
private void queueTask(SipContainerFutureTask task, TimerQueue myQueue) {
myQueue.pendingLock.lock();
try {
if (task.nextExecution < System.currentTimeMillis()) {
addTask(task.task);
if (task.scheduleNextExecution()) {
myQueue.pendingQueue.add(task);
}
} else {
myQueue.pendingQueue.add(task);
if (task == myQueue.pendingQueue.first()) {
myQueue.pendingCV.signal();
}
}
} finally {
myQueue.pendingLock.unlock();
}
}
public static SipContainerThreadPool getInstance() {
return _singletonInstance;
}
public boolean isShutdown() {
//if( myThreadPool == null ) return true;
//return myThreadPool.isShutdown();
return isShutdown;
}
public void shutdown() {
//myThreadPool.shutdown();
isShutdown = true;
}
public void initialize(int threadPoolSize) {
//myThreadPool = new ScheduledThreadPoolExecutor(threadPoolSize, new ThreadPoolExecutor.CallerRunsPolicy() );
//myThreadPool.prestartAllCoreThreads();
//TODO check if the queue should be set.
isShutdown = false;
_threads = new SipContainerThreadPoolThread[threadPoolSize];
for (int i = 0; i < threadPoolSize; i++) {
_threads[i] = new SipContainerThreadPoolThread();
_threads[i].start();
}
timerThreads = new SipContainerTimerThread[timerQueues.length];
for (int i = 0; i < timerQueues.length; i++) {
timerThreads[i] = new SipContainerTimerThread(timerQueues[i]);
timerThreads[i].start();
}
}
public void initialize(Pipeline threadpool) {
grizzlyThreadPool = threadpool;
//TODO Prestart threads for timers, redesign for Grizzly!!!
initialize(grizzlyThreadPool.getMaxThreads());
}
/**
* Executes the Runnable in a thread pool of worker threads
*/
public void execute(Callable c) {
if (grizzlyThreadPool != null) {
try {
grizzlyThreadPool.execute(c);
} catch (PipelineFullException e) {
if (_log.isLoggable(Level.SEVERE)) {
_log.log(Level.SEVERE, "sip.stack.network.error_executing_task", e);
}
throw new RuntimeException(e);
}
} else {
// myThreadPool.execute(new MyRunnable(r));
addTask(c);
}
}
public ScheduledFuture<?> schedule(Callable command, long delay, TimeUnit unit) {
//return myThreadPool.schedule(command, delay, unit);
TimerQueue q = nextTimerQueue();
SipContainerFutureTask task = new SipContainerFutureTask(command, delay, unit, q);
queueTask(task, q);
return task;
}
public ScheduledFuture<?> scheduleAtFixedRate(Callable command, long initialDelay, long period, TimeUnit unit) {
//return myThreadPool.scheduleAtFixedRate(command, initialDelay, period, unit);
TimerQueue q = nextTimerQueue();
SipContainerFutureTask task = new SipContainerFutureTask(command, initialDelay, period, unit, q);
queueTask(task, q);
return task;
}
public ScheduledFuture<?> scheduleWithFixedDelay(Callable command, long initialDelay, long delay, TimeUnit unit) {
//return myThreadPool.scheduleWithFixedDelay(command, initialDelay, delay, unit);
TimerQueue q = nextTimerQueue();
SipContainerFutureTask task = new SipContainerFutureTask(command, initialDelay, delay, unit, q);
queueTask(task, q);
return task;
}
public void purge() {
//myThreadPool.purge();
}
public Integer getThreadPoolSize() {
if (grizzlyThreadPool != null) {
return new Integer(grizzlyThreadPool.getCurrentThreadCount());
}
//return new Integer(myThreadPool.getPoolSize());
return _threads.length;
}
private void initTimerQueues() {
int nQueues = Integer.getInteger("org.jvnet.glassfish.comms.sip.timer.queues", 1);
if (_log.isLoggable(Level.FINER)) {
_log.log(Level.FINER, "SipContainerThreadPool: running " + nQueues + " timer queues");
}
timerQueues = new TimerQueue[nQueues];
for (int i = 0; i < nQueues; i++)
timerQueues[i] = new TimerQueue();
}
private synchronized TimerQueue nextTimerQueue() {
if (_log.isLoggable(Level.FINEST)) {
_log.log(Level.FINEST, "timerQueues="
+ Arrays.toString(timerQueues) + ",curTimerQueue="
+ curTimerQueue);
}
TimerQueue q = timerQueues[curTimerQueue];
curTimerQueue = (++curTimerQueue) % timerQueues.length;
return q;
}
public static void main(String[] args) throws Exception {
SipContainerThreadPool pool = SipContainerThreadPool.getInstance();
pool.initialize(10);
System.out.println("About to execute new thread immediately");
pool.execute(new MyTest("Executing immediately"));
//ScheduledFuture sf = pool.schedule(new MyTest("Should execute in 30 seconds"), 30, TimeUnit.SECONDS);
ScheduledFuture sf2 = pool.scheduleAtFixedRate(new MyTest("Should execute every 3 seconds after 10"), 10, 3, TimeUnit.SECONDS);
ScheduledFuture sf3 = pool.schedule(new MyTest("Should never execute because cancelled"), 60, TimeUnit.SECONDS);
Thread.sleep(30 * 1000);
sf3.cancel(false);
pool.schedule(new MyTest("Should execute after about 40 total seconds"), 5, TimeUnit.SECONDS);
sf2.cancel(false);
System.out.println("all done");
Thread.sleep(30 * 1000);
System.exit(-1);
}
private static class TimerQueue {
SortedSet<SipContainerFutureTask> pendingQueue = new TreeSet<SipContainerFutureTask>();
Lock pendingLock = new ReentrantLock();
Condition pendingCV = pendingLock.newCondition();
}
private class SipContainerThreadPoolThread extends Thread {
public void run() {
while (!isShutdown) {
Callable task = getTask();
try {
task.call();
} catch (Throwable t) {
if (_log.isLoggable(Level.SEVERE)) {
_log.log(Level.SEVERE, "sip.stack.network.error_executing_task", t);
}
}
}
}
}
private class SipContainerTimerThread extends Thread {
TimerQueue myQueue;
public SipContainerTimerThread(TimerQueue q) {
myQueue = q;
}
public void run() {
try {
myQueue.pendingLock.lock();
while (!isShutdown) {
SipContainerFutureTask t = null;
if (!myQueue.pendingQueue.isEmpty()) {
t = myQueue.pendingQueue.first();
}
if (t == null) {
try {
myQueue.pendingCV.await();
} catch (InterruptedException ie) {
}
} else {
if (t.isCancelled) {
// discard and continue
myQueue.pendingQueue.remove(t);
continue;
}
long waitTime = t.nextExecution - System.currentTimeMillis();
if (waitTime < 0) {
myQueue.pendingQueue.remove(t);
try {
addTask(t.task);
} catch (Throwable e) {
if (_log.isLoggable(Level.WARNING)) {
_log.log(Level.WARNING, "Unexpected error in timer task", e);
}
}
if (t.scheduleNextExecution()) {
myQueue.pendingQueue.add(t);
}
} else {
try {
myQueue.pendingCV.await(waitTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
}
}
}
}
} finally {
myQueue.pendingLock.unlock();
}
}
}
private class SipContainerFutureTask implements ScheduledFuture {
Callable task;
long nextExecution;
volatile boolean isCancelled;
long interval;
TimerQueue myQueue;
SipContainerFutureTask(Callable command, long initialDelay, long period, TimeUnit unit, TimerQueue q) {
task = command;
nextExecution = System.currentTimeMillis() + unit.toMillis(initialDelay);
interval = unit.toMillis(period);
myQueue = q;
}
SipContainerFutureTask(Callable command, long delay, TimeUnit unit, TimerQueue q) {
task = command;
nextExecution = System.currentTimeMillis() + unit.toMillis(delay);
interval = 0L;
myQueue = q;
}
public String toString() {
String taskClass = ((task == null) ? "NULL" : ("" + task.getClass()));
return "SipTask " + taskClass + ", Next Execution " + nextExecution + ", Interval is " + interval + ", isCancelled = " + isCancelled;
}
public boolean scheduleNextExecution() {
if (interval == 0) {
return false;
}
nextExecution = System.currentTimeMillis() + interval;
return true;
}
public int compareTo(Delayed d) {
// Return 0, only of the objects are the same.
if (d == this) {
return 0;
}
long diff = (nextExecution - ((SipContainerFutureTask) d).nextExecution);
if (diff < 0) {
return -1;
} else if (diff > 0) {
return 1;
}
// Dont care about the sequence, in case of a tie.
// TODO. Explore possibility of using a sequencer may be an AtomicLong...
return -1;
}
public long getDelay(TimeUnit unit) {
return unit.convert(nextExecution - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
public boolean cancel(boolean mayInterrupt) {
myQueue.pendingLock.lock();
try {
isCancelled = true;
task = null;
return myQueue.pendingQueue.remove(this);
} finally {
myQueue.pendingLock.unlock();
}
}
public Object get() {
throw new UnsupportedOperationException("Not implemented");
}
public Object get(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException("Not implemented");
}
public boolean isCancelled() {
return isCancelled;
}
public boolean isDone() {
throw new UnsupportedOperationException("Not implemented");
}
}
private static class MyTest implements Callable {
String s;
MyTest(String s) {
this.s = s;
}
public Object call() {
System.out.println(s + ": ran at " + new java.util.Date() + " on thread " + Thread.currentThread());
return null;
}
}
}