org.fusesource.hawtdispatch.internal.NioDispatchSource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hawtdispatch Show documentation
Show all versions of hawtdispatch Show documentation
HawtDispatch: The libdispatch style API for Java
/**
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
*
* 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
*/
package org.fusesource.hawtdispatch.internal;
import org.fusesource.hawtdispatch.*;
import java.io.IOException;
import java.nio.channels.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.lang.String.format;
import static org.fusesource.hawtdispatch.DispatchQueue.QueueType.THREAD_QUEUE;
/**
*
* Implements the DispatchSource interface.
*
*
* Description: An NioDispatchSource is associated with one SelectableChannel
* but supports being registered on selectors associated with different thread.
* Usually just one at time tho.
*
*
* @author cmacnaug
* @author Hiram Chirino
*/
final public class NioDispatchSource extends AbstractDispatchObject implements DispatchSource {
public static final boolean DEBUG = false;
final SelectableChannel channel;
volatile DispatchQueue selectorQueue;
final AtomicBoolean canceled = new AtomicBoolean();
final int interestOps;
Runnable cancelHandler;
Runnable eventHandler;
// These fields are only accessed by the ioManager's thread.
public static class KeyState {
int readyOps;
SelectionKey key;
NioAttachment attachment;
@Override
public String toString() {
return "{ready: "+opsToString(readyOps)+" }";
}
}
private static String opsToString(int ops) {
ArrayList sb = new ArrayList();
if( (ops & SelectionKey.OP_ACCEPT) != 0) {
sb.add("ACCEPT");
}
if( (ops & SelectionKey.OP_CONNECT) != 0) {
sb.add("CONNECT");
}
if( (ops & SelectionKey.OP_READ) != 0) {
sb.add("READ");
}
if( (ops & SelectionKey.OP_WRITE) != 0) {
sb.add("WRITE");
}
return sb.toString();
}
final ThreadLocal keyState = new ThreadLocal();
public NioDispatchSource(HawtDispatcher dispatcher, SelectableChannel channel, int interestOps, DispatchQueue targetQueue) {
if( interestOps == 0 ) {
throw new IllegalArgumentException("invalid interest ops");
}
this.channel = channel;
this.selectorQueue = pickThreadQueue(dispatcher, targetQueue);
this.interestOps = interestOps;
this.suspended.incrementAndGet();
this.setTargetQueue(targetQueue);
}
static private DispatchQueue pickThreadQueue(HawtDispatcher dispatcher, DispatchQueue targetQueue) {
// Try to select a thread queue associated /w the target if available..
DispatchQueue selectorQueue = targetQueue;
while( selectorQueue.getQueueType()!=THREAD_QUEUE && selectorQueue.getTargetQueue() !=null ) {
selectorQueue = selectorQueue.getTargetQueue();
}
// otherwise.. pick the thread queue with the fewest registered selection
// keys.
if( selectorQueue.getQueueType()!=THREAD_QUEUE ) {
WorkerThread[] threads = dispatcher.DEFAULT_QUEUE.workers.getThreads();
WorkerThread min = threads[0];
int minSize = min.getNioManager().getSelector().keys().size();
for( int i=1; i < threads.length; i++) {
int s = threads[i].getNioManager().getSelector().keys().size();
if( s < minSize ) {
minSize = s;
min = threads[i];
}
}
selectorQueue = min.getDispatchQueue();
}
return selectorQueue;
}
@Override
protected void onStartup() {
if( eventHandler==null ) {
throw new IllegalArgumentException("eventHandler must be set");
}
register_on(selectorQueue);
}
public void cancel() {
if( canceled.compareAndSet(false, true) ) {
selectorQueue.execute(new Runnable(){
public void run() {
internal_cancel();
}
});
}
}
void internal_cancel() {
key_cancel();
if( cancelHandler!=null ) {
targetQueue.execute(cancelHandler);
}
}
private void key_cancel() {
// Deregister...
KeyState state = keyState.get();
if( state==null ) {
return;
}
debug("canceling source");
state.attachment.sources.remove(this);
if( state.attachment.sources.isEmpty() ) {
debug("canceling key.");
// This will make sure that the key is removed
// from the ioManager.
state.key.cancel();
// Running a select to remove the canceled key.
Selector selector = WorkerThread.currentWorkerThread().getNioManager().getSelector();
try {
selector.selectNow();
} catch (CancelledKeyException ignore) {
} catch (IOException e) {
debug(e, "Error canceling");
}
}
debug("Canceled selector on "+WorkerThread.currentWorkerThread().getDispatchQueue().getLabel() );
keyState.remove();
}
private void register_on(final DispatchQueue queue) {
queue.execute(new Runnable(){
public void run() {
assert keyState.get()==null;
if(DEBUG) debug("Registering interest %s", opsToString(interestOps));
Selector selector = WorkerThread.currentWorkerThread().getNioManager().getSelector();
try {
KeyState state = new KeyState();
state.key = channel.keyFor(selector);
if( state.key==null ) {
state.key = channel.register(selector, interestOps);
state.attachment = new NioAttachment();
state.key.attach(state.attachment);
} else {
state.attachment = (NioAttachment)state.key.attachment();
}
state.attachment.sources.add(NioDispatchSource.this);
keyState.set(state);
try {
// the key could be canceled by now..
state.key.interestOps(state.key.interestOps()|interestOps);
} catch (CancelledKeyException e) {
state.attachment.cancel(state.key);
}
} catch (ClosedChannelException e) {
debug(e, "could not register with selector");
}
debug("Registered");
}
});
}
public void fire(final int readyOps) {
final KeyState state = keyState.get();
if( state==null ) {
return;
}
state.readyOps |= readyOps;
if( state.readyOps!=0 && !isSuspended()&& !isCanceled() ) {
state.readyOps = 0;
targetQueue.execute(new Runnable() {
public void run() {
if( !isSuspended() && !isCanceled()) {
if(DEBUG) debug("fired %s", opsToString(readyOps));
try {
eventHandler.run();
} catch (Throwable e) {
e.printStackTrace();
}
updateInterest();
}
}
});
}
}
private void updateInterest() {
if( isCurrent(selectorQueue) ) {
if( !isSuspended() && !isCanceled() ) {
if(DEBUG) debug("adding interest: %s", opsToString(interestOps));
KeyState state = keyState.get();
if( state==null ) {
return;
}
if( state.key.isValid() ) {
state.key.interestOps(state.key.interestOps()|interestOps);
}
}
} else {
selectorQueue.execute(new Runnable(){
public void run() {
if( !isSuspended() && !isCanceled() ) {
if(DEBUG) debug("adding interest: %d", opsToString(interestOps));
KeyState state = keyState.get();
if( state==null ) {
return;
}
if( state.key.isValid() ) {
state.key.interestOps(state.key.interestOps()|interestOps);
}
}
}
});
}
}
private boolean isCurrent(DispatchQueue q) {
WorkerThread thread = WorkerThread.currentWorkerThread();
if( thread == null )
return false;
return thread.getDispatchQueue() == q;
}
@Override
protected void onSuspend() {
debug("onSuspend");
super.onSuspend();
}
@Override
protected void onResume() {
debug("onResume");
if( isCurrent(selectorQueue) ) {
KeyState state = keyState.get();
if( state==null || state.readyOps==0 ) {
updateInterest();
} else {
fire(state.readyOps);
}
} else {
selectorQueue.execute(new Runnable(){
public void run() {
KeyState state = keyState.get();
if( state==null || state.readyOps==0 ) {
updateInterest();
} else {
fire(interestOps);
}
}
});
}
}
public boolean isCanceled() {
return canceled.get();
}
public void setCancelHandler(Runnable cancelHandler) {
this.cancelHandler = cancelHandler;
}
public void setEventHandler(Runnable eventHandler) {
this.eventHandler = eventHandler;
}
public Void getData() {
return null;
}
public void setTargetQueue(DispatchQueue next) {
super.setTargetQueue(next);
// The target thread queue might be different. Optimize by switching the selector to it.
// Do we need to switch selector threads?
DispatchQueue queue = next;
while( queue.getQueueType()!=THREAD_QUEUE && queue.getTargetQueue() !=null ) {
queue = queue.getTargetQueue();
}
if( queue.getQueueType()==THREAD_QUEUE && queue!=selectorQueue ) {
DispatchQueue previous = selectorQueue;
debug("Switching to "+queue.getLabel());
register_on(queue);
selectorQueue = queue;
if( previous!=null ) {
previous.execute(new Runnable(){
public void run() {
key_cancel();
}
});
}
}
}
protected void debug(String str, Object... args) {
if (DEBUG) {
String thread = Thread.currentThread().getName();
String target ="";
if( Dispatch.getCurrentQueue()!=null ) {
target = Dispatch.getCurrentQueue().getLabel() + " | ";
}
System.out.println(format("DEBUG | %s | #%0#10x | %s%s", thread, System.identityHashCode(this), target, format(str, args)));
}
}
protected void debug(Throwable thrown, String str, Object... args) {
if (DEBUG) {
if (str != null) {
debug(str, args);
}
if (thrown != null) {
thrown.printStackTrace();
}
}
}
}