/* * 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 java.io.IOException; import java.nio.channels.Channel; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import java.util.concurrent.Executor; import org.simpleframework.util.thread.Daemon; /** * 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. Canceling 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 Distributor { /** * This is the queue that is used to provide the operations. */ private ActionQueue ready; /** * This is used to determine the operations that need canceling. */ private ChannelMap table; /** * This is used to execute the operations that are ready. */ private Executor executor; /** * This is the selector used to select for interested events. */ private Selector selector; /** * 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; /** * This is used to determine when the distributor is closed. */ private volatile boolean dead; /** * 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.selector = Selector.open(); this.table = new ChannelMap(); this.ready = new ActionQueue(); this.latch = new Latch(); this.executor = executor; this.cancel = cancel; this.expiry = expiry; this.start(); } /** * 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. */ @Override public void run() { this.execute(); this.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 (!this.dead) { try { this.register(); this.cancel(); this.expire(); this.distribute(); } catch (Exception e) { continue; } } } /** * 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 * signaled when all operations are purged. This will allow them to return * ensuring no operations linger. */ private void purge() { try { this.register(); this.cancel(); this.drain(); } catch (Exception e) { return; } } /** * 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 */ @Override public void process(Operation task, int require) throws IOException { Action action = new ExecuteAction(task, require, this.expiry); if (this.dead) throw new IOException("Distributor is closed"); this.ready.offer(action); this.selector.wakeup(); } /** * 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. */ @Override public void close() throws IOException { this.dead = true; this.selector.wakeup(); this.latch.close(); } /** * 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 drain() throws IOException { Set<SelectionKey> set = this.selector.keys(); for (SelectionKey key : set) { this.expire(key, Long.MAX_VALUE); } this.selector.close(); this.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 canceled if the I/O event they wait on has not arisen. This will * enables the canceled operation to be canceled so that the resources it * occupies can be released. */ private void expire() throws IOException { Set<SelectionKey> set = this.selector.keys(); if (this.cancel) { long time = System.currentTimeMillis(); if (this.update <= time) { for (SelectionKey key : set) { this.expire(key, time); } this.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 canceled if the I/O event they wait on has not arisen. This will * enables the canceled operation to be canceled so that the resources it * occupies can be released. * * @param key * this is the selection key for the operation */ private void expire(SelectionKey key, long time) throws IOException { Action task = (Action) key.attachment(); if (task != null) { long expiry = task.getExpiry(); if (expiry < time) { this.expire(key, task); } } } /** * 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 canceled if the I/O event they wait on has not arisen. This will * enables the canceled operation to be canceled so that the resources it * occupies can be released. * * @param key * this is the selection key for the operation * @param action * this is the actual action to be canceled */ private void expire(SelectionKey key, Action action) throws IOException { Action cancel = new CancelAction(action); if (key != null) { key.attach(cancel); key.cancel(); } this.process(key); } /** * 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<SelectionKey> list = this.table.values(); for (SelectionKey key : list) { key.cancel(); } this.table.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 (!this.ready.isEmpty()) { Action action = this.ready.poll(); if (action != null) { this.register(action); } } } /** * Here the specified <code>Operation</code> object is registered with the * selector. If the associated channel had previously been canceled 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 { int require = action.getInterest(); this.register(action, require); } /** * Here the specified <code>Operation</code> object is registered with the * selector. If the associated channel had previously been canceled 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 require * this is the bit-mask value for interested events */ private void register(Action action, int require) throws IOException { SelectableChannel channel = action.getChannel(); SelectionKey key = this.table.remove(channel); if (key != null) { key.interestOps(require); key.attach(action); } else { if (channel.isOpen()) { this.select(channel, require).attach(action); } } } /** * 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 channel * this is the channel to register for selection * @param require * this is the I/O bit mask that is required * * @return this returns the selection key used for selection */ private SelectionKey select(SelectableChannel channel, int require) throws IOException { return channel.register(this.selector, require); } /** * 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 (this.selector.select(5000) > 0) { if (!this.dead) { this.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 { Set<SelectionKey> keys = this.selector.selectedKeys(); Iterator<SelectionKey> ready = keys.iterator(); while (ready.hasNext()) { SelectionKey key = ready.next(); if (key != null) { ready.remove(); } if (key != null) { this.process(key); } } } /** * This will use the specified selection key to acquire the channel and * <code>Operation</code> associated with it to hand 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. * * @param key * this is the selection key that is to be processed */ private void process(SelectionKey key) throws IOException { Runnable task = (Runnable) key.attachment(); Channel channel = key.channel(); if (this.cancel) { this.table.put(channel, key); } this.executor.execute(task); } /** * The <code>ChannelMap</code> object is used to store selection keys using * a given channel. This is used to determine which of the registered * operations has been executed, and thus should be removed from the * selector so that it does not break on further selections of the * interested operations. */ private class ChannelMap extends HashMap<Channel, SelectionKey> { /** * Constructor for the <code>ChannelMap</code> object. This is used to * create a map for channels to selection keys. This will allows the * selection keys that need to be canceled quickly to be retrieved using * the associated channel object. */ public ChannelMap() { super(); } } }