/*
* ActionDistributor.java February 2007
*
* Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
*
* 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.simpleframework.transport.reactor;
import static java.nio.channels.SelectionKey.OP_READ;
import static java.nio.channels.SelectionKey.OP_WRITE;
import static org.simpleframework.transport.reactor.ReactorEvent.CHANNEL_CLOSED;
import static org.simpleframework.transport.reactor.ReactorEvent.CLOSE_SELECTOR;
import static org.simpleframework.transport.reactor.ReactorEvent.ERROR;
import static org.simpleframework.transport.reactor.ReactorEvent.EXECUTE_ACTION;
import static org.simpleframework.transport.reactor.ReactorEvent.INVALID_KEY;
import static org.simpleframework.transport.reactor.ReactorEvent.READ_INTEREST_READY;
import static org.simpleframework.transport.reactor.ReactorEvent.REGISTER_INTEREST;
import static org.simpleframework.transport.reactor.ReactorEvent.REGISTER_READ_INTEREST;
import static org.simpleframework.transport.reactor.ReactorEvent.REGISTER_WRITE_INTEREST;
import static org.simpleframework.transport.reactor.ReactorEvent.SELECT;
import static org.simpleframework.transport.reactor.ReactorEvent.SELECT_CANCEL;
import static org.simpleframework.transport.reactor.ReactorEvent.SELECT_EXPIRED;
import static org.simpleframework.transport.reactor.ReactorEvent.UPDATE_INTEREST;
import static org.simpleframework.transport.reactor.ReactorEvent.UPDATE_READ_INTEREST;
import static org.simpleframework.transport.reactor.ReactorEvent.UPDATE_WRITE_INTEREST;
import static org.simpleframework.transport.reactor.ReactorEvent.WRITE_INTEREST_READY;
import java.io.IOException;
import java.nio.channels.Channel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import org.simpleframework.common.thread.Daemon;
import org.simpleframework.transport.trace.Trace;
/**
* The <code>ActionDistributor</code> is used to execute operations
* that have an interested I/O event ready. This acts much like a
* scheduler would in that it delays the execution of the operations
* until such time as the associated <code>SelectableChannel</code>
* has an interested I/O event ready.
* <p>
* This distributor has two modes, one mode is used to cancel the
* channel once an I/O event has occurred. This means that the channel
* is removed from the <code>Selector</code> so that the selector
* does not break when asked to select again. cancelling the channel
* is useful when the operation execution may not fully read the
* payload or when the operation takes a significant amount of time.
*
* @see org.simpleframework.transport.reactor.ExecutorReactor
*/
class ActionDistributor extends Daemon implements OperationDistributor {
/**
* This is used to determine the operations that need cancelling.
*/
private Map<Channel, ActionSet> executing;
/**
* This is used to keep track of actions currently in selection.
*/
private Map<Channel, ActionSet> selecting;
/**
* This is the queue that is used to invalidate channels.
*/
private Queue<Channel> invalid;
/**
* This is the queue that is used to provide the operations.
*/
private Queue<Action> pending;
/**
* This is the selector used to select for interested events.
*/
private ActionSelector selector;
/**
* This is used to execute the operations that are ready.
*/
private Executor executor;
/**
* This is used to signal when the distributor has closed.
*/
private Latch latch;
/**
* This is the duration in milliseconds the operation expires in.
*/
private long expiry;
/**
* This is time in milliseconds when the next expiry will occur.
*/
private long update;
/**
* This is used to determine the mode the distributor uses.
*/
private boolean cancel;
/**
* Constructor for the <code>ActionDistributor</code> object. This
* will create a distributor that distributes operations when those
* operations show that they are ready for a given I/O event. The
* interested I/O events are provided as a bitmask taken from the
* actions of the <code>SelectionKey</code>. Distribution of the
* operations is passed to the provided executor object.
*
* @param executor this is the executor used to execute operations
*/
public ActionDistributor(Executor executor) throws IOException {
this(executor, true);
}
/**
* Constructor for the <code>ActionDistributor</code> object. This
* will create a distributor that distributes operations when those
* operations show that they are ready for a given I/O event. The
* interested I/O events are provided as a bitmask taken from the
* actions of the <code>SelectionKey</code>. Distribution of the
* operations is passed to the provided executor object.
*
* @param executor this is the executor used to execute operations
* @param cancel should the channel be removed from selection
*/
public ActionDistributor(Executor executor, boolean cancel) throws IOException {
this(executor, cancel, 120000);
}
/**
* Constructor for the <code>ActionDistributor</code> object. This
* will create a distributor that distributes operations when those
* operations show that they are ready for a given I/O event. The
* interested I/O events are provided as a bitmask taken from the
* actions of the <code>SelectionKey</code>. Distribution of the
* operations is passed to the provided executor object.
*
* @param executor this is the executor used to execute operations
* @param cancel should the channel be removed from selection
* @param expiry this the maximum idle time for an operation
*/
public ActionDistributor(Executor executor, boolean cancel, long expiry) throws IOException {
this.selecting = new LinkedHashMap<Channel, ActionSet>();
this.executing = new LinkedHashMap<Channel, ActionSet>();
this.pending = new ConcurrentLinkedQueue<Action>();
this.invalid = new ConcurrentLinkedQueue<Channel>();
this.selector = new ActionSelector();
this.latch = new Latch();
this.executor = executor;
this.cancel = cancel;
this.expiry = expiry;
this.start();
}
/**
* This is used to process the <code>Operation</code> object. This
* will wake up the selector if it is currently blocked selecting
* and register the operations associated channel. Once the
* selector is awake it will acquire the operation from the queue
* and register the associated <code>SelectableChannel</code> for
* selection. The operation will then be executed when the channel
* is ready for the interested I/O events.
*
* @param task this is the task that is scheduled for distribution
* @param require this is the bit-mask value for interested events
*/
public void process(Operation task, int require) throws IOException {
Action action = new ExecuteAction(task, require, expiry);
if(!isActive()) {
throw new IOException("Distributor is closed");
}
pending.offer(action);
selector.wake();
}
/**
* This is used to close the distributor such that it cancels all
* of the registered channels and closes down the selector. This
* is used when the distributor is no longer required, after the
* close further attempts to process operations will fail.
*/
public void close() throws IOException {
stop();
selector.wake();
latch.close();
}
/**
* This returns the number of channels that are currently selecting
* with this distributor. When busy this can get quite high, however
* it must return to zero as soon as all tasks have completed.
*
* @return return the number of channels currently selecting
*/
public int size() {
return selecting.size();
}
/**
* Performs the execution of the distributor. Each distributor runs
* on an asynchronous thread to the <code>Reactor</code> which is
* used to perform the selection on a set of channels. Each time
* there is a new operation to be processed this will take the
* operation from the ready queue, cancel all outstanding channels,
* and register the operations associated channel for selection.
*/
public void run() {
try {
execute();
} finally {
purge();
}
}
/**
* Performs the execution of the distributor. Each distributor runs
* on an asynchronous thread to the <code>Reactor</code> which is
* used to perform the selection on a set of channels. Each time
* there is a new operation to be processed this will take the
* operation from the ready queue, cancel all outstanding channels,
* and register the operations associated channel for selection.
*/
private void execute() {
while(isActive()) {
try {
register();
cancel();
expire();
distribute();
validate();
} catch(Exception cause) {
report(cause);
}
}
}
/**
* This will purge all the actions from the distributor when the
* distributor ends. If there are any threads waiting on the close
* to finish they are signalled when all operations are purged.
* This will allow them to return ensuring no operations linger.
*/
private void purge() {
try {
register();
cancel();
clear();
} catch(Exception cause) {
report(cause);
}
}
/**
* This method is called to ensure that if there is a global
* error that each action will know about it. Such an issue could
* be file handle exhaustion or an out of memory error. It is
* also possible that a poorly behaving action could cause an
* issue which should be know the the entire system.
*
* @param cause this is the exception to report
*/
private void report(Exception cause) {
Set<Channel> channels = selecting.keySet();
for(Channel channel : channels) {
ActionSet set = selecting.get(channel);
Action[] list = set.list();
for(Action action : list) {
Operation operation = action.getOperation();
Trace trace = operation.getTrace();
try {
trace.trace(ERROR, cause);
} catch(Exception e) {
invalid.offer(channel);
}
}
}
invalid.clear();
}
/**
* Here we perform an expire which will take all of the registered
* sockets and expire it. This ensures that the operations can be
* executed within the executor and the cancellation of the sockets
* can be performed. Once this method has finished then all of
* the operations will have been scheduled for execution.
*/
private void clear() throws IOException {
List<ActionSet> sets = selector.registeredSets();
for(ActionSet set : sets) {
Action[] list = set.list();
for(Action action : list) {
Operation task = action.getOperation();
Trace trace = task.getTrace();
try {
trace.trace(CLOSE_SELECTOR);
expire(set, Long.MAX_VALUE);
} catch(Exception cause) {
trace.trace(ERROR, cause);
}
}
}
selector.close();
latch.signal();
}
/**
* This method is used to expire registered operations that remain
* idle within the selector. Operations specify a time at which
* point they wish to be cancelled if the I/O event they wait on
* has not arisen. This will enables the cancelled operation to be
* cancelled so that the resources it occupies can be released.
*/
private void expire() throws IOException {
List<ActionSet> sets = selector.registeredSets();
if(cancel) {
long time = System.currentTimeMillis();
if(update <= time) {
for(ActionSet set : sets) {
expire(set, time);
}
update = time +10000;
}
}
}
/**
* This method is used to expire registered operations that remain
* idle within the selector. Operations specify a time at which
* point they wish to be if the I/O event they wait on
* has not arisen. This will enables the cancelled operation to be
* cancelled so that the resources it occupies can be released.
*
* @param set this is the selection set check for expired actions
* @param time this is the time to check the expiry against
*/
private void expire(ActionSet set, long time) throws IOException {
Action[] actions = set.list();
SelectionKey key = set.key();
if(key.isValid()) {
int mask = key.interestOps();
for(Action action : actions) {
int interest = action.getInterest();
long expiry = action.getExpiry();
if(expiry < time) {
expire(set, action);
mask &= ~interest;
}
}
update(set, mask);
}
}
/**
* This is used to update the interested operations of a set of
* actions. If there are no interested operations the set will be
* cancelled, otherwise the selection key will be updated with the
* new operations provided by the bitmask.
*
* @param set this is the action set that is to be updated
* @param interest this is the bitmask containing the operations
*/
private void update(ActionSet set, int interest) throws IOException {
SelectionKey key = set.key();
if(interest == 0) {
Channel channel = key.channel();
selecting.remove(channel);
key.cancel();
} else {
key.interestOps(interest);
}
}
/**
* This method is used to expire registered operations that remain
* idle within the selector. Operations specify a time at which
* point they wish to be cancelled if the I/O event they wait on
* has not arisen. This will enables the cancelled operation to be
* cancelled so that the resources it occupies can be released.
*
* @param set this is the action set containing the actions
* @param action this is the actual action to be cancelled
*/
private void expire(ActionSet set, Action action) throws IOException {
Action cancel = new CancelAction(action);
if(set != null) {
Operation task = action.getOperation();
Trace trace = task.getTrace();
int interest = action.getInterest();
try {
trace.trace(SELECT_EXPIRED, interest);
set.remove(interest);
execute(cancel);
} catch(Exception cause) {
trace.trace(ERROR, cause);
}
}
}
/**
* This method is used to perform simple validation. It ensures
* that directly after the processing loop any channels that
* are registered that have been cancelled or are closed will
* be removed from the selecting map and rejected.
*/
private void validate() throws IOException {
Set<Channel> channels = selecting.keySet();
for(Channel channel : channels) {
ActionSet set = selecting.get(channel);
SelectionKey key = set.key();
if(!key.isValid()) {
invalid.offer(channel);
}
}
for(Channel channel : invalid) {
invalidate(channel);
}
invalid.clear();
}
/**
* This method is used to remove the channel from the selecting
* registry. It is rare that this will every happen, however it
* is important that tasks are cleared out in this manner as it
* could lead to a memory leak if left for a long time.
*
* @param channel this is the channel being validated
*/
private void invalidate(Channel channel) throws IOException {
ActionSet set = selecting.remove(channel);
Action[] list = set.list();
for(Action action : list) {
Operation task = action.getOperation();
Trace trace = task.getTrace();
try {
trace.trace(INVALID_KEY);
execute(action); // reject
} catch(Exception cause) {
trace.trace(ERROR, cause);
}
}
}
/**
* This is used to cancel any selection keys that have previously
* been selected with an interested I/O event. Performing a cancel
* here ensures that on a the next select the associated channel
* is not considered, this ensures the select does not break.
*/
private void cancel() throws IOException {
Collection<ActionSet> list = executing.values();
for(ActionSet set : list) {
Action[] actions = set.list();
for(Action action : actions) {
Operation task = action.getOperation();
Trace trace = task.getTrace();
trace.trace(SELECT_CANCEL);
}
set.cancel();
set.clear();
}
executing.clear();
}
/**
* Here all the enqueued <code>Operation</code> objects will be
* registered for selection. Each operations channel is used for
* selection on the interested I/O events. Once the I/O event
* occurs for the channel the operation is scheduled for execution.
*/
private void register() throws IOException {
while(!pending.isEmpty()) {
Action action = pending.poll();
if(action != null) {
SelectableChannel channel = action.getChannel();
ActionSet set = executing.remove(channel);
if(set == null) {
set = selecting.get(channel);
}
if(set != null) {
update(action, set);
} else {
register(action);
}
}
}
}
/**
* Here the specified <code>Operation</code> object is registered
* with the selector. If the associated channel had previously
* been cancelled it is removed from the cancel map to ensure it
* is not removed from the selector when cancellation is done.
*
* @param action this is the operation that is to be registered
*/
private void register(Action action) throws IOException {
SelectableChannel channel = action.getChannel();
Operation task = action.getOperation();
Trace trace = task.getTrace();
try {
if(channel.isOpen()) {
trace.trace(SELECT);
select(action);
} else {
trace.trace(CHANNEL_CLOSED);
selecting.remove(channel);
execute(action); // reject
}
}catch(Exception cause) {
trace.trace(ERROR, cause);
}
}
/**
* Here the specified <code>Operation</code> object is registered
* with the selector. If the associated channel had previously
* been cancelled it is removed from the cancel map to ensure it
* is not removed from the selector when cancellation is done.
*
* @param action this is the operation that is to be registered
* @param set this is the action set to register the action with
*/
private void update(Action action, ActionSet set) throws IOException {
Operation task = action.getOperation();
Trace trace = task.getTrace();
SelectionKey key = set.key();
int interest = action.getInterest();
int current = key.interestOps();
int updated = current | interest;
try {
if(OP_READ == (interest & OP_READ)) {
trace.trace(UPDATE_READ_INTEREST);
}
if(OP_WRITE == (interest & OP_WRITE)) {
trace.trace(UPDATE_WRITE_INTEREST);
}
trace.trace(UPDATE_INTEREST, updated);
key.interestOps(updated);
set.attach(action);
} catch(Exception cause) {
trace.trace(ERROR, cause);
}
}
/**
* This method is used to perform an actual select on a channel. It
* will register the channel with the internal selector using the
* required I/O event bit mask. In order to ensure that selection
* is performed correctly the provided channel must be connected.
*
* @param action this is the operation that is to be registered
*
* @return this returns the selection key used for selection
*/
private void select(Action action) throws IOException {
SelectableChannel channel = action.getChannel();
Operation task = action.getOperation();
Trace trace = task.getTrace();
int interest = action.getInterest();
if(interest > 0) {
ActionSet set = selector.register(channel, interest);
if(OP_READ == (interest & OP_READ)) {
trace.trace(REGISTER_READ_INTEREST);
}
if(OP_WRITE == (interest & OP_WRITE)) {
trace.trace(REGISTER_WRITE_INTEREST);
}
trace.trace(REGISTER_INTEREST, interest);
set.attach(action);
selecting.put(channel, set);
}
}
/**
* This method is used to perform the select and if required queue
* the operations that are ready for execution. If the selector
* is woken up without any ready channels then this will return
* quietly. If however there are a number of channels ready to be
* processed then they are handed to the executor object and
* marked as ready for cancellation.
*/
private void distribute() throws IOException {
if(selector.select(5000) > 0) {
if(isActive()) {
process();
}
}
}
/**
* This will iterate over the set of selection keys and process each
* of them. The <code>Operation</code> associated with the selection
* key is handed to the executor to perform the channel operation.
* Also, if configured to cancel, this method will add the channel
* and the associated selection key to the cancellation map.
*/
private void process() throws IOException{
List<ActionSet> ready = selector.selectedSets();
for(ActionSet set : ready) {
process(set);
remove(set);
}
}
/**
* This will use the specified action set to acquire the channel
* and <code>Operation</code> associated with it to hand to the
* executor to perform the channel operation.
*
* @param set this is the set of actions that are to be processed
*/
private void process(ActionSet set) throws IOException {
Action[] actions = set.ready();
for(Action action : actions) {
Operation task = action.getOperation();
Trace trace = task.getTrace();
int interest = action.getInterest();
try {
if(OP_READ == (interest & OP_READ)) {
trace.trace(READ_INTEREST_READY, interest);
}
if(OP_WRITE == (interest & OP_WRITE)) {
trace.trace(WRITE_INTEREST_READY, interest);
}
execute(action);
} catch(Exception cause) {
trace.trace(ERROR, cause);
}
}
}
/**
* This method ensures that references to the actions and channel
* are cleared from this instance. To ensure there are no memory
* leaks it is important to clear out all actions and channels.
* Also, if configured to cancel executing actions this will
* register the channel and actions to cancel on the next loop.
*
* @param set this is the set of actions that are to be removed
*/
private void remove(ActionSet set) throws IOException {
Channel channel = set.channel();
SelectionKey key = set.key();
if(key.isValid()) {
int interest = set.interest();
int ready = key.readyOps();
if(cancel) {
int remaining = interest & ~ready;
if(remaining == 0) {
executing.put(channel, set);
} else {
key.interestOps(remaining);
}
set.remove(ready);
}
} else {
selecting.remove(channel);
}
}
/**
* This is where the action is handed off to the executor. Before
* the action is executed a trace event is generated, this will
* ensure that the entry and exit points can be tracked. It is
* also useful in debugging performance issues and memory leaks.
*
* @param action this is the action to execute
*/
private void execute(Action action) {
Operation task = action.getOperation();
Trace trace = task.getTrace();
int interest = action.getInterest();
try {
trace.trace(EXECUTE_ACTION, interest);
executor.execute(action);
} catch(Exception cause) {
trace.trace(ERROR, cause);
}
}
}