/**
* Copyright (C) 2012 FuseSource, Inc.
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.hawtdispatch.internal;
import org.fusesource.hawtdispatch.*;
import org.fusesource.hawtdispatch.jmx.JmxService;
import java.nio.channels.SelectableChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import static org.fusesource.hawtdispatch.DispatchPriority.DEFAULT;
/**
* Implements a simple dispatch system.
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
final public class HawtDispatcher implements Dispatcher {
public final static ThreadLocal<HawtDispatchQueue> CURRENT_QUEUE = new ThreadLocal<HawtDispatchQueue>();
final GlobalDispatchQueue DEFAULT_QUEUE;
private final Object HIGH_MUTEX = new Object();
private GlobalDispatchQueue HIGH_QUEUE;
private final Object LOW_MUTEX = new Object();
private GlobalDispatchQueue LOW_QUEUE;
private final String label;
volatile TimerThread timerThread;
private final int threads;
private volatile boolean profile;
final int drains;
final boolean jmx;
final AtomicInteger shutdownState = new AtomicInteger(0);
volatile Thread.UncaughtExceptionHandler uncaughtExceptionHandler=null;
public HawtDispatcher(DispatcherConfig config) {
this.threads = config.getThreads();
this.label = config.getLabel();
this.profile = config.isProfile();
this.drains = config.getDrains();
this.jmx = config.isJmx();
if( this.jmx ) {
try {
JmxService.register(this);
} catch (Throwable ignore) {
}
}
DEFAULT_QUEUE = new GlobalDispatchQueue(this, DispatchPriority.DEFAULT, config.getThreads());
DEFAULT_QUEUE.start();
DEFAULT_QUEUE.profile(profile);
timerThread = new TimerThread(this);
timerThread.start();
}
public void shutdown() {
// shutdown == 1 stop new dispatch after requests..
if( shutdownState.compareAndSet(0, 1) ) {
// give every one a chance to notice
// the state change.
sleep(100);
timerThread.shutdown(new Task() {
public void run() {
// all outstanding timers will have been
// queued for execution
// shutdown == 2 stop queues from accepting
// new executions
shutdownState.set(2);
// new state change..
sleep(100);
// Wait for the execution queues to drain..
DEFAULT_QUEUE.shutdown();
if( LOW_QUEUE!=null ) {
LOW_QUEUE.shutdown();
}
if(HIGH_QUEUE!=null) {
HIGH_QUEUE.shutdown();
}
// shutdown == 3 means we are fully drained.
shutdownState.set(3);
}
}, DEFAULT_QUEUE);
}
if( this.jmx ) {
try {
JmxService.unregister(this);
} catch (Throwable ignore) {
}
}
}
private void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
}
}
public void restart() {
if( shutdownState.compareAndSet(3, 0) ) {
timerThread = new TimerThread(this);
timerThread.start();
DEFAULT_QUEUE.start();
if( LOW_QUEUE!=null ) {
LOW_QUEUE.start();
}
if(HIGH_QUEUE!=null) {
HIGH_QUEUE.start();
}
} else {
throw new IllegalStateException("Not shutdown yet.");
}
}
public DispatchQueue getGlobalQueue() {
return getGlobalQueue(DEFAULT);
}
public GlobalDispatchQueue getGlobalQueue(DispatchPriority priority) {
switch (priority) {
case DEFAULT:
return DEFAULT_QUEUE;
case HIGH:
// lazy load the high queue to avoid creating it's thread if the application is not using
// the queue at all.
synchronized(HIGH_MUTEX) {
if( HIGH_QUEUE==null ) {
HIGH_QUEUE = new GlobalDispatchQueue(this, DispatchPriority.HIGH, threads);
HIGH_QUEUE.start();
HIGH_QUEUE.profile(profile);
}
return HIGH_QUEUE;
}
case LOW:
// lazy load the low queue to avoid creating it's thread if the application is not using
// the queue at all.
synchronized(LOW_MUTEX) {
if( LOW_QUEUE==null ) {
LOW_QUEUE = new GlobalDispatchQueue(this, DispatchPriority.LOW, threads);
LOW_QUEUE.start();
LOW_QUEUE.profile(profile);
}
return LOW_QUEUE;
}
}
throw new AssertionError("switch missing case");
}
public SerialDispatchQueue createQueue(String label) {
SerialDispatchQueue rc = new SerialDispatchQueue(label);
rc.setTargetQueue(getGlobalQueue());
rc.profile(profile);
return rc;
}
public DispatchSource createSource(SelectableChannel channel, int interestOps, DispatchQueue queue) {
return new NioDispatchSource(this, channel, interestOps, queue);
}
public <Event, MergedEvent> CustomDispatchSource<Event, MergedEvent> createSource(EventAggregator<Event, MergedEvent> aggregator, DispatchQueue queue) {
return new HawtCustomDispatchSource(this, aggregator, queue);
}
public String getLabel() {
return label;
}
public DispatchQueue getCurrentQueue() {
return CURRENT_QUEUE.get();
}
public ThreadDispatchQueue getCurrentThreadQueue() {
WorkerThread thread = WorkerThread.currentWorkerThread();
if( thread ==null ) {
return null;
}
return thread.getDispatchQueue();
}
public DispatchQueue[] getThreadQueues(DispatchPriority priority) {
return getGlobalQueue(priority).getThreadQueues();
}
final static public WeakHashMap<HawtDispatchQueue, Object> queues = new WeakHashMap<HawtDispatchQueue, Object>();
void track(HawtDispatchQueue queue) {
synchronized (queues) {
queues.put(queue, Boolean.TRUE);
}
}
void untrack(HawtDispatchQueue queue) {
synchronized (queues) {
queues.remove(queue);
}
}
public void profile(boolean on) {
profile = on;
}
public boolean profile() {
return profile;
}
public List<Metrics> metrics() {
synchronized (queues) {
ArrayList<Metrics> rc = new ArrayList<Metrics>();
for( HawtDispatchQueue queue : queues.keySet() ) {
if( queue!=null ) {
Metrics metrics = queue.metrics();
if( metrics!=null ) {
rc.add(metrics);
}
}
}
return rc;
}
}
String assertMessage(String label) {
StringBuilder sb = new StringBuilder();
sb.append("Dispatch queue '");
if(label!=null ) {
sb.append(label);
} else {
sb.append("<no-label>");
}
sb.append("' was not executing, (currently executing: '");
DispatchQueue q = getCurrentQueue();
if( q!=null ) {
if( q.getLabel()!=null ) {
sb.append(q.getLabel());
} else {
sb.append("<no-label>");
}
} else {
sb.append("<not-dispatched>");
}
sb.append("')");
return sb.toString();
}
public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler;
}
public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
this.uncaughtExceptionHandler = uncaughtExceptionHandler;
}
}