/* * Copyright (c) 2004-2007 Sun Microsystems, Inc. All rights reserved. * * The Sun Project JXTA(TM) Software License * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by Sun Microsystems, Inc. for JXTA(TM) technology." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must * not be used to endorse or promote products derived from this software * without prior written permission. For written permission, please contact * Project JXTA at http://www.jxta.org. * * 5. Products derived from this software may not be called "JXTA", nor may * "JXTA" appear in their name, without prior written permission of Sun. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JXTA is a registered trademark of Sun Microsystems, Inc. in the United * States and other countries. * * Please see the license information page at : * <http://www.jxta.org/project/www/license.html> for instructions on use of * the license in source files. * * ==================================================================== * * This software consists of voluntary contributions made by many individuals * on behalf of Project JXTA. For more information on Project JXTA, please see * http://www.jxta.org. * * This license is based on the BSD license adopted by the Apache Foundation. */ package net.jxta.endpoint; import net.jxta.peergroup.PeerGroupID; import java.io.IOException; import java.io.InterruptedIOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; /** * Extends Channel Messenger behaviour to provide asynchronous message sending * via queuing. */ public abstract class AsyncChannelMessenger extends ChannelMessenger { /* * Logger * private final static transient Logger LOG = Logger.getLogger(AsyncChannelMessenger.class.getName()); */ /** * {@code true} if we have deliberately closed our one message input queue. */ private boolean inputClosed = false; /** * {@code true} if we have deliberately stopped sending. */ private boolean outputClosed = false; /** * Actions that we defer to after returning from event methods. In other * words, they cannot be done with the lock held, or they require calling * more event methods. */ private enum DeferredAction { /** * No action deferred. */ ACTION_NONE, /** * Must send the current message. */ ACTION_SEND, /** * Must report failure to connect. */ ACTION_CONNECT } /** * The current deferred action. */ private DeferredAction deferredAction = DeferredAction.ACTION_NONE; /** * The messages queue. */ private final BlockingQueue<PendingMessage> queue; /** * State lock and engine. */ private final AsyncChannelMessengerState stateMachine; /** * Our statemachine implementation; just connects the standard MessengerState action methods to * this object. */ private class AsyncChannelMessengerState extends MessengerState { protected AsyncChannelMessengerState(boolean connected, MessengerStateListener listener) { super(connected, listener); } /* * The required action methods. */ /** * {@inheritDoc} */ @Override protected void connectAction() { deferredAction = DeferredAction.ACTION_CONNECT; } /** * {@inheritDoc} */ @Override protected void startAction() { deferredAction = DeferredAction.ACTION_SEND; } /** * {@inheritDoc} */ @Override protected void closeInputAction() { // We're synchronized here. (invoked from stateMachine) inputClosed = true; } /** * {@inheritDoc} */ @Override protected void closeOutputAction() { // We're synchronized here. (invoked from stateMachine) outputClosed = true; } /** * {@inheritDoc} */ @Override protected void failAllAction() { // The queue is now closed, so we can rest assured that the last // message is really the last one. This is a synchronous action. The // state machine assumes that it is done when we return. There is no // need to signal completion with an idleEvent. PendingMessage theMsg; while (true) { theMsg = null; synchronized (stateMachine) { theMsg = queue.poll(); } if (theMsg == null) { return; } Message currentMsg = theMsg.msg; Throwable currentFailure = theMsg.failure; if (currentFailure == null) { currentFailure = new IOException("Messenger unexpectedly closed"); } OutgoingMessageEvent event = new OutgoingMessageEvent(currentMsg, currentFailure); currentMsg.setMessageProperty(Messenger.class, event); } } } /** * The representation of a queued message. It is shared between this * abstract class and any implementation. */ protected static class PendingMessage { final Message msg; final String service; final String param; Throwable failure; PendingMessage(Message msg, String service, String param) { this.msg = msg; this.service = service; this.param = param; this.failure = null; } } /** * Create a new AsyncChannelMessenger. * * @param baseAddress The network address messages go to; regardless of * service, param, or group. * @param redirection Group to which the messages must be redirected. This * is used to implement the automatic group segregation which has become a * de-facto standard. If not null, the unique portion of the specified * groupID is prepended with {@link #InsertedServicePrefix} and inserted in * every message's destination address in place of the the original service * name, which gets shifted into the beginning of the service parameter. The * opposite is done on arrival to restore the original destination address * before the message is delivered to the listener in the the specified * group. Messages that already bear a group redirection are not affected. * @param origService The default destination service for messages sent * without specifying a different service. * @param origServiceParam The default destination service parameter for * messages sent without specifying a different service parameter. * @param queueSize the queue size that channels should have. * @param connected true if the channel is created in the connected state. */ public AsyncChannelMessenger(EndpointAddress baseAddress, PeerGroupID redirection, String origService, String origServiceParam, int queueSize, boolean connected) { super(baseAddress, redirection, origService, origServiceParam); stateMachine = new AsyncChannelMessengerState(connected, distributingListener); queue = new ArrayBlockingQueue<PendingMessage>(queueSize); } /** * {@inheritDoc} */ public final void close() { DeferredAction action; synchronized (stateMachine) { stateMachine.closeEvent(); action = eventCalled(true); } // We called an event. State may have changed. notifyChange(); performDeferredAction(action); } /** * This internal method does the common part of sending the message on * behalf of both sendMessageN and sendMessageB. * <p/>It is not quite possible to implement sendMessageB as a wrapper * around sendMessageN without some internal cooperation. At least not in * an efficient manner. sendMessageB must not set the message property: * either it fails and throws, or it returns successfully and the property * is set later. This is required so that messages can be retried when * failing synchronously (through a blocking messenger typically, but the * semantic has to be uniform). * <p/>Each of sendMessageB and sendMessageN takes care of status reporting * on its own terms. * * @param msg the message to send * @param rService destination service * @param rServiceParam destination param * @return The outcome from that one attempt. {@code true} means done. * {@code false} means saturated. When {@code true} is returned, it means * that the fate of the message will be decided asynchronously, so we do * not have any details, yet. * @throws IOException is thrown if this messenger is closed. * @throws InterruptedException if interrupted */ private boolean sendMessageCommon(Message msg, String rService, String rServiceParam) throws IOException, InterruptedException { String service = effectiveService(rService); String serviceParam = effectiveParam(rService, rServiceParam); boolean queued = true; boolean change = false; DeferredAction action = DeferredAction.ACTION_NONE; synchronized (stateMachine) { if (inputClosed) { throw new IOException("This messenger is closed. It cannot be used to send messages."); } boolean wasEmpty = queue.isEmpty(); if (queue.remainingCapacity() > 1) { queue.put(new PendingMessage(msg, service, serviceParam)); // Still not saturated. If we weren't idle either, then nothing worth mentionning. if (wasEmpty) { change = true; stateMachine.msgsEvent(); action = eventCalled(false); } } else if (1 == queue.remainingCapacity()) { queue.put(new PendingMessage(msg, service, serviceParam)); // Now saturated. stateMachine.saturatedEvent(); action = eventCalled(false); change = true; } else { // Was already saturated. queued = false; } } if (queued && change) { // If not queued, there was no change of condition as far as // outsiders are concerned. (redundant saturatedEvent, only // defensive; to guarantee statemachine in sync). else, if the // saturation state did not change, we have no state change to // notify. notifyChange(); } performDeferredAction(action); // Before we return, make sure that this channel remains referenced if // it has messages. It could become unreferenced if it is not yet // resolved and the application lets go of it after sending messages. // This means that we may need to do something only in the resolpending // and resolsaturated cases. The way we do this test, there can be false // positives. They're dealt with as part of the action that is carried // out. if ((stateMachine.getState() & (Messenger.RESOLPENDING | Messenger.RESOLSATURATED)) != 0) { resolPendingImpl(); } return queued; } /** * {@inheritDoc} */ public final boolean sendMessageN(Message msg, String rService, String rServiceParam) { try { if (sendMessageCommon(msg, rService, rServiceParam)) { // If it worked the message is queued; the outcome will be notified later. return true; } // Non-blocking and queue full: report overflow. msg.setMessageProperty(Messenger.class, OutgoingMessageEvent.OVERFLOW); } catch (IOException oie) { msg.setMessageProperty(Messenger.class, new OutgoingMessageEvent(msg, oie)); } catch (InterruptedException interrupted) { msg.setMessageProperty(Messenger.class, new OutgoingMessageEvent(msg, interrupted)); } return false; } /** * {@inheritDoc} */ public final void sendMessageB(Message msg, String rService, String rServiceParam) throws IOException { try { while (true) { // if sendMessageCommon says "true" it worked. if (sendMessageCommon(msg, rService, rServiceParam)) { return; } // Do a shallow check on the queue. If it seems empty (without getting into a critical section to // verify it), then yielding is good bet. It is a lot cheaper and smoother than waiting. // Note the message should be enqueued now. yielding makes sense now if the queue is empty if (queue.isEmpty()) { Thread.yield(); } // If we reached this far, it is neither closed, nor ok. So it was saturated. synchronized (stateMachine) { // Cheaper than waitState. sendMessageCommon already does the relevant state checks. stateMachine.wait(); } } } catch (InterruptedException ie) { InterruptedIOException iie = new InterruptedIOException("Message send interrupted"); iie.initCause(ie); throw iie; } } /** * {@inheritDoc} */ public final void resolve() { DeferredAction action; synchronized (stateMachine) { stateMachine.resolveEvent(); action = eventCalled(true); } notifyChange(); performDeferredAction(action); // we expect connect but let the state machine decide. } /** * {@inheritDoc} */ public final int getState() { return stateMachine.getState(); } /** * {@inheritDoc} */ @Override public final Messenger getChannelMessenger(PeerGroupID redirection, String service, String serviceParam) { // Channels don't make channels. return null; } /** * Three exposed methods may need to inject new events in the system: * sendMessageN, close, and shutdown. * Since they can all cause actions, and since connectAction and * startAction are deferred, it seems possible that one of the actions * caused by send, close, or shutdown be called while connectAction or * startAction are in progress. * <p/>However, the state machine gives us a few guarantees: connectAction * and startAction can never nest. We will not be asked to perform one while * still performing the other. Only the synchronous actions closeInput, * closeOutput, or failAll can possibly be requested in the interval. We * could make more assumptions and simplify the code, but rather keep at * least some flexibility. * <p/> * DEAD LOCK WARNING: the implementor's method invoke some of our call backs * while synchronized. Then our call backs synchronize on the state machine * in here. This nesting order must always be respected. As a result, we can * never invoke implementors methods while synchronized. Hence the * deferredAction processing. * * @param action the action */ private void performDeferredAction(DeferredAction action) { switch (action) { case ACTION_SEND: startImpl(); break; case ACTION_CONNECT: connectImpl(); break; } } /** * A shortHand for a frequently used sequence. MUST be called while * synchronized on stateMachine. * * @param notifyAll If {@code true} then this is a life-cycle event and all * waiters on the stateMachine should be notified. If {@code false} then * only a single waiter will be notified for simple activity events. * @return the deferred action. */ private DeferredAction eventCalled(boolean notifyAll) { DeferredAction action = deferredAction; deferredAction = DeferredAction.ACTION_NONE; if (notifyAll) { stateMachine.notifyAll(); } else { stateMachine.notify(); } return action; } /* * Implement the methods that our shared messenger will use to report progress. */ /** * The implementation will invoke this method when it becomes resolved, * after connectImpl was invoked. */ protected void up() { DeferredAction action; synchronized (stateMachine) { stateMachine.upEvent(); action = eventCalled(true); } notifyChange(); performDeferredAction(action); // we expect start but let the state machine decide. } /** * The implementation invokes this method when it becomes broken. */ protected void down() { DeferredAction action; synchronized (stateMachine) { stateMachine.downEvent(); action = eventCalled(true); } notifyChange(); performDeferredAction(action); // we expect connect but let the state machine decide. } /** * Here, we behave like a queue to the shared messenger. When we report * being empty, though, we're automatically removed from the active queues * list. We'll go back there the next time we have something to send by * calling startImpl. * * @return pending message */ protected PendingMessage peek() { PendingMessage theMsg; DeferredAction action = DeferredAction.ACTION_NONE; synchronized (stateMachine) { // We like the msg to keep occupying space in the queue until it's // out the door. That way, idleness (that is, not currently working // on a message), is always consistent with queue emptyness. theMsg = queue.peek(); if (theMsg == null) { stateMachine.idleEvent(); action = eventCalled(false); // We do not notifyChange, here, because, if the queue is empty, // it was already notified when the last message was popped. The // call to idleEvent is only defensive programming to make extra // sure the state machine is in sync. return null; } if (outputClosed) { // We've been asked to stop sending. Which, if we were sending, // must be notified by either an idle event or a down // event. Nothing needs to happen to the shared messenger. We're // just a channel. stateMachine.downEvent(); action = eventCalled(true); theMsg = null; } } notifyChange(); performDeferredAction(action); // we expect none but let the state machine decide. return theMsg; } /** * Returns the number of elements in this collection. If this collection * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns * <tt>Integer.MAX_VALUE</tt>. * * @return the number of elements in this collection */ protected int size() { return queue.size(); } /** * One message done. Update the saturated/etc state accordingly. * * @return true if there are more messages after the one we removed. */ protected boolean poll() { boolean result; DeferredAction action; synchronized (stateMachine) { queue.poll(); if (queue.peek() == null) { stateMachine.idleEvent(); action = eventCalled(false); result = false; } else { stateMachine.msgsEvent(); action = eventCalled(false); result = true; } } notifyChange(); performDeferredAction(action); // we expect none but let the state machine decide. return result; } /** * We invoke this method to be placed on the list of channels that have * message to send. * <p/> * NOTE that it is the shared messenger responsibility to synchronize so * that we cannot be added to the active list just before we get removed * due to reporting an empty queue in parallel. So, if we report an empty * queue and have a new message to send before the shared messenger removes * us form the active list, startImpl will block until the removal is done. * Then we'll be added back. * <p/> * If it cannot be done, it means that the shared messenger is no longer * usable. It may call down() in sequence. Out of defensiveness, it should * do so without holding its lock. */ protected abstract void startImpl(); /** * We invoke this method to be placed on the list of channels that are * waiting for resolution. * <p/> * If it cannot be done, it means that the shared messenger is no longer * usable. It may call down() in sequence. Out of defensiveness, it should * do so without holding its lock. If the messenger is already resolved it * may call up() in sequence. Same wisdom applies. It is a good idea to * create channels in the resolved state if the shared messenger is already * resolved. That avoids this extra contortion. */ protected abstract void connectImpl(); /** * This is invoked to inform the implementation that this channel is now in * the resolPending or resolSaturated state. This is specific to this type * of channels. The shared messenger must make sure that this channel * remains strongly referenced, even though it is not resolved, because * there are messages in it. It is valid for an application to let go of a * channel after sending a message, even if the channel is not yet * resolved. The message will go if/when the channel resolves. This method * may be invoked redundantly and even once the channel is no longer among * the one awaiting resolution. The implementation must be careful to * ignore such calls. */ protected abstract void resolPendingImpl(); }