/*
* 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.logging.Logging;
import net.jxta.peergroup.PeerGroupID;
import java.io.IOException;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is a messenger meant to be shared by multiple channels and automatically
* distribute the available bandwidth among the channels. This one is implemented
* with a dedicated background thread.
*/
public abstract class ThreadedMessenger extends AbstractMessenger implements Runnable {
/**
* Logger
*/
private final static transient Logger LOG = Logger.getLogger(ThreadedMessenger.class.getName());
/**
* Our thread group.
*/
private final static transient ThreadGroup myThreadGroup = new ThreadGroup("Threaded Messengers");
/**
* The logical destination address of the other party (if we know it).
*/
private volatile EndpointAddress logicalDestination = null;
/**
* true if we have deliberately closed our input queue.
*/
private volatile boolean inputClosed = false;
/**
* Need to know which group the transports we use live in, so that we can suppress channel redirection when in the same group.
* This is currently the norm.
*/
private PeerGroupID homeGroupID = null;
/**
* The duration in milliseconds which the background thread will remain
* idle before quitting.
*/
private static final long THREAD_IDLE_DEAD = 15000;
/*
* 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. Because this messenger can take only one message at a
* time, actions do not cascade much. It may happen that the invoking thread
* is required to perform closure after performing send. That's about it.
* In addition, there's always only one deferred action per event. The only
* actions that cluster are closeInput and closeOutput. We do not defer
* those.
*/
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 current background thread.
*/
private volatile Thread bgThread = null;
/**
* The number of messages which may be queued for in each channel.
*/
private final int channelQueueSize;
/**
* The active channel queue.
*/
private final BlockingQueue<ThreadedMessengerChannel> activeChannels = new LinkedBlockingQueue<ThreadedMessengerChannel>();
/**
* The resolving channels set. This is unordered. We use a weak hash map because abandoned channels could otherwise
* accumulate in-there until the resolution attempt completes. A buggy application could easily do much damage.
* <p/>
* Note: a channel with at least one message in it is not considered abandoned. To prevent it from disappearing we set a
* strong reference as the value in the map. A buggy application can do great damage, still, by queuing a single message
* and then abandoning the channel. This is has to be dealt with at another level; limiting the number of channels
* per application, or having a global limit on messages...TBD.
*/
private final WeakHashMap<ThreadedMessengerChannel, ThreadedMessengerChannel> resolvingChannels = new WeakHashMap<ThreadedMessengerChannel, ThreadedMessengerChannel>(4);
/**
* A default channel where we put messages that are send directly through
* this messenger rather than via one of its channels.
*/
private ThreadedMessengerChannel defaultChannel = null;
/**
* State lock and engine.
*/
private final ThreadedMessengerState stateMachine;
/**
* The implementation of channel messenger that getChannelMessenger returns:
*/
private class ThreadedMessengerChannel extends AsyncChannelMessenger {
public ThreadedMessengerChannel(EndpointAddress baseAddress, PeerGroupID redirection, String origService, String origServiceParam, int queueSize, boolean connected) {
super(baseAddress, redirection, origService, origServiceParam, queueSize, connected);
}
/**
* {@inheritDoc}
* <p/>
* We're supposed to return the complete destination, including
* service and param specific to that channel. It is not clear, whether
* this should include the cross-group mangling, though. Historically,
* it does not.
*/
public EndpointAddress getLogicalDestinationAddress() {
return logicalDestination;
}
/**
* {@inheritDoc}
*/
@Override
protected void startImpl() {
if (!addToActiveChannels(this)) {
// We do not need to hold our lock to call this, and it is just as well since it could re-enter.
down();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void connectImpl() {
// If it cannot be done, it is because we known that we will never be able to generate the resulting event. That means
// that either the shared messenger is already resolved, or that it is already dead. In that case, we invoke down/up
// in sequence accordingly.
//
// NOTE: the shared messenger may become dead 1 ns from now...that or 1 hour makes no difference, the channel will
// notice when it first tries to send, in that case. The otherway around is more obvious: If the shared messenger is
// not USABLE, it cannot come back.
//
// addToResolvingChannels() garantees us that if it returns true, either of the channel's down or up methods will be
// invoked at some point.
if (!addToResolvingChannels(this)) {
if ((ThreadedMessenger.this.getState() & USABLE) != 0) {
up();
} else {
down();
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void resolPendingImpl() {
// If this channel is still among the ones pending resolution, make sure
// it becomes strongly referenced.
strongRefResolvingChannel(this);
}
}
/**
* Our statemachine implementation; just connects the standard AbstractMessengerState action methods to
* this object.
*/
private class ThreadedMessengerState extends MessengerState {
protected ThreadedMessengerState(MessengerStateListener listener) {
super(false, listener);
}
/*
* The required action methods.
*/
/**
* {@inheritDoc}
*/
@Override
protected void connectAction() {
deferAction(DeferredAction.ACTION_CONNECT);
}
/**
* {@inheritDoc}
*/
@Override
protected void startAction() {
deferAction(DeferredAction.ACTION_SEND);
}
/**
* {@inheritDoc}
* <p/>
* This is a synchronous action. The state machine assumes that it
* is done when we return. There is No need (nor means) to signal
* completion. No need for synchronization either: we're already
* synchronized.
*/
@Override
protected void closeInputAction() {
inputClosed = true;
ThreadedMessengerChannel[] channels = resolvingChannels.keySet().toArray(new ThreadedMessengerChannel[0]);
resolvingChannels.clear();
int i = channels.length;
while (i-- > 0) {
channels[i].down();
}
channels = null;
}
/**
* {@inheritDoc}
*/
@Override
protected void closeOutputAction() {
// This will break the cnx; thereby causing a down event if we have a send in progress.
// If the cnx does not break before the current message is sent, then the message will be sent successfully,
// resulting in an idle event. Either of these events is enough to complete the shutdown process.
closeImpl();
}
/**
* {@inheritDoc}
* <p/>
* The input is now closed, so we can rest assured that the last
* channel 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.
* No need for synchronization either: we're already synchronized.
*/
@Override
protected void failAllAction() {
while (true) {
ThreadedMessengerChannel theChannel;
theChannel = activeChannels.poll();
if (theChannel == null) {
break;
}
theChannel.down();
}
}
}
/**
* Create a new ThreadedMessenger.
*
* @param homeGroupID the group that this messenger works for. This is the group of the endpoint service or transport
* that created this messenger.
* @param destination where messages should be addressed to
* @param logicalDestination the expected logical address of the destination. Pass null if unknown/irrelevant
* @param channelQueueSize The queue size that channels should have.
*/
public ThreadedMessenger(PeerGroupID homeGroupID, EndpointAddress destination, EndpointAddress logicalDestination, int channelQueueSize) {
super(destination);
this.homeGroupID = homeGroupID;
stateMachine = new ThreadedMessengerState(distributingListener);
this.logicalDestination = logicalDestination;
this.channelQueueSize = channelQueueSize;
}
/**
* Runs the state machine until there's nothing left to do.
* <p/>
* Three exposed methods may need to inject new events in the system: sendMessageN, close, and shutdown. Since they can both
* 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: All the actions except closeInput and closeOutput have an *end*
* event. No state transition that results in an action other than closeInput or closeOutput, may occur until the end event
* for an on-going action has been called.
* <p/>
* We perform closeInput and closeOutput on the fly, so none of the exposed methods are capable of producing deferred actions
* while an action is already deferred. So, there is at most one deferred action after returning from an event method,
* regardless the number of concurrent threads invoking the exposed methods, and it can only happen once per deferred action
* performed.
*/
public void run() {
try {
while (true) {
switch (nextAction()) {
case ACTION_NONE:
return;
case ACTION_SEND:
send();
break;
case ACTION_CONNECT:
connect();
break;
}
}
} catch (Throwable any) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "Uncaught throwable in background thread", any);
// Hope the next thread has more luck. It'll need it.
}
} finally {
synchronized (stateMachine) {
bgThread = null;
}
}
}
private void deferAction(DeferredAction action) {
deferredAction = action;
if (bgThread == null) {
bgThread = new Thread(myThreadGroup, this, "ThreadedMessenger for " + getDestinationAddress());
bgThread.setDaemon(true);
bgThread.start();
}
}
private DeferredAction nextAction() {
long quitAt = System.currentTimeMillis() + THREAD_IDLE_DEAD;
synchronized (stateMachine) {
while (deferredAction == DeferredAction.ACTION_NONE) {
// Still nothing to do. Is it time to quit, or where we just awakened for nothing ?
if (System.currentTimeMillis() > quitAt) {
// Ok. Time to quit.
return DeferredAction.ACTION_NONE;
}
// We do not need to wakeup exactly on the deadline, so there's no need to
// recompute the deadline. THREAD_IDLE_DEAD is comparatively short.
try {
stateMachine.wait(THREAD_IDLE_DEAD);
} catch (InterruptedException ie) {
// Only shutdown can force termination.
Thread.interrupted();
}
}
DeferredAction action = deferredAction;
deferredAction = DeferredAction.ACTION_NONE;
return action;
}
}
/**
* Performs the ACTION_SEND deferred action: sends the messages in our channel queues until there's none left or
* we are forced to stop by connection breakage.
* @throws InterruptedException if interrupted
*/
private void send() throws InterruptedException {
ThreadedMessengerChannel theChannel;
synchronized (stateMachine) {
theChannel = activeChannels.peek();
if (theChannel == null) {
// No notifyChange: this is defensive code. NotifyChange() should have been called already.
stateMachine.idleEvent();
stateMachine.notifyAll();
return;
}
}
while (true) {
AsyncChannelMessenger.PendingMessage theMsg = theChannel.peek();
if (theMsg == null) {
// done with that channel for now. (And it knows it). Move to the next channel. Actually
// it should have been removed when we popped the last message, except if we went down upon sending it.
// In that later case, we leave the channel queue as is so that we cannot have to report, idle
// in the same time than down.
synchronized (stateMachine) {
activeChannels.poll();
theChannel = activeChannels.peek();
if (theChannel != null) {
continue; // Nothing changes; we do not call msgsEvent because we never call saturatedEvent either.
}
// Done with all channels. We're now idle.
stateMachine.idleEvent();
stateMachine.notifyAll();
}
notifyChange();
return;
}
Message currentMsg = theMsg.msg;
String currentService = theMsg.service;
String currentParam = theMsg.param;
try {
sendMessageBImpl(currentMsg, currentService, currentParam);
} catch (Throwable any) {
// When the current message fails, we leave it in there. sendMessageBImpl does not report failures. So that we can retry if
// applicable. It is up to us to report failures. See failall in AsyncChannel. However, there is a risk that a bad
// message causes this messenger to go down repeatedly. We need some kind of safeguard. So, if there's already a failure
// recorded for this message, we bounce it.
synchronized (stateMachine) {
if (theMsg.failure != null) {
theChannel.poll();
currentMsg.setMessageProperty(Messenger.class, new OutgoingMessageEvent(currentMsg, theMsg.failure));
} else {
theMsg.failure = any;
}
stateMachine.downEvent();
stateMachine.notifyAll();
}
notifyChange();
return;
}
synchronized (stateMachine) {
// Remove the message sent
theChannel.poll();
// Rotate the queues (Things are quite a bit simpler if there's a single still active channel
// and it's frequent, so it's worth checking)
boolean empty = (theChannel.peek() == null);
if ((activeChannels.size() != 1) || empty) {
activeChannels.poll();
if (!empty) {
// We're not done with that channel. Put it back at the end
activeChannels.put(theChannel);
}
// Get the next channel.
theChannel = activeChannels.peek();
if (theChannel == null) {
// Done with all channels. We're now idle.
stateMachine.idleEvent();
stateMachine.notifyAll();
}
} // else {continue to use the current channel}
}
if (theChannel == null) {
notifyChange();
// We're about to go wait(). Yielding is a good bet. It is
// very inexpenssive and may be all it takes to get a new job
// queued.
Thread.yield();
return;
}
}
}
/**
* Performs the ACTION_CONNECT deferred action. Generates a down event if it does not work.
*/
private void connect() {
boolean worked = connectImpl();
ThreadedMessengerChannel[] channels = null;
synchronized (stateMachine) {
if (worked) {
// we can now get the logical destination from the underlying implementation (likely obtained from a transport
// messenger)
EndpointAddress effectiveLogicalDest = getLogicalDestinationImpl();
if (logicalDestination == null) {
// We did not know what was supposed to be on the other side. Anything will do.
logicalDestination = effectiveLogicalDest;
stateMachine.upEvent();
channels = resolvingChannels.keySet().toArray(new ThreadedMessengerChannel[0]);
resolvingChannels.clear();
} else if (logicalDestination.equals(effectiveLogicalDest)) {
// Good. It's what we expected.
stateMachine.upEvent();
channels = resolvingChannels.keySet().toArray(new ThreadedMessengerChannel[0]);
resolvingChannels.clear();
} else {
// Ooops, not what we wanted. Can't connect then. (force close the underlying cnx).
closeImpl();
stateMachine.downEvent();
}
} else {
stateMachine.downEvent();
}
stateMachine.notifyAll();
}
// If it worked, we need to tell all the channels that were waiting for resolution.
// If it did not work, the outcome depends upon what will happen after the down event.
// It's ok to do that outside of sync. Channel.up may synchronize, but it never calls
// this class while synchronized.
if (channels != null) {
int i = channels.length;
while (i-- > 0) {
channels[i].up();
}
channels = null;
}
notifyChange();
}
/*
* Messenger API top level methods.
*/
/**
* The endpoint service may call this to cause an orderly closure of its messengers.
*/
protected final void shutdown() {
synchronized (stateMachine) {
stateMachine.shutdownEvent();
stateMachine.notifyAll();
}
notifyChange();
}
/**
* {@inheritDoc}
*/
public EndpointAddress getLogicalDestinationAddress() {
// If it's not resolved, we can't know what the logical destination is, unless we had an expectation.
// And if we had, the messenger will fail as soon as we discover that the expectation is wrong.
// In most if not all cases, either we have an expectation, or the messenger comes already resolved.
// Otherwise, if you need the logical destination, you must resolve first. We do not want this method
// to be blocking.
return logicalDestination;
}
/**
* {@inheritDoc}
*/
public void close() {
synchronized (stateMachine) {
stateMachine.closeEvent();
stateMachine.notifyAll();
}
notifyChange();
}
/**
* {@inheritDoc}
* <p/>
* In this case, this method is here out of principle but is not really expected to be invoked. The normal way
* of using a ThreadedMessenger is through its channels. We do provide a default channel that all invokers that go around
* channels will share. That could be useful to send rare out of band messages for example.
*/
public final boolean sendMessageN(Message msg, String service, String serviceParam) {
synchronized (stateMachine) {
if (defaultChannel == null) {
// Need a default channel.
defaultChannel = new ThreadedMessengerChannel(getDestinationAddress(), null, null, null, channelQueueSize, false);
}
}
return defaultChannel.sendMessageN(msg, service, serviceParam);
}
/**
* {@inheritDoc}
*/
public final void sendMessageB(Message msg, String service, String serviceParam) throws IOException {
synchronized (stateMachine) {
if (defaultChannel == null) {
// Need a default channel.
defaultChannel = new ThreadedMessengerChannel(getDestinationAddress(), null, null, null, channelQueueSize, false);
}
}
defaultChannel.sendMessageB(msg, service, serviceParam);
}
private boolean addToActiveChannels(ThreadedMessengerChannel channel) {
synchronized (stateMachine) {
if (inputClosed) {
return false;
}
try {
activeChannels.put(channel);
} catch (InterruptedException failed) {
Thread.interrupted();
return false;
}
// There are items in the queue now.
stateMachine.msgsEvent();
// We called an event. The state may have changed. Notify waiters.
stateMachine.notifyAll();
}
notifyChange();
return true;
}
private void strongRefResolvingChannel(ThreadedMessengerChannel channel) {
// If, and only if, this channel is already among the resolving channels, add a strong ref
// to it. This is invoked when a message is queued to that channel while it is still
// resolving. However we must verify its presence in the resolvingChannels map: this method
// may be called while the channel has been removed from the list, but has not been told
// yet.
synchronized (stateMachine) {
if (resolvingChannels.containsKey(channel)) {
resolvingChannels.put(channel, channel);
}
}
}
private boolean addToResolvingChannels(ThreadedMessengerChannel channel) {
synchronized (stateMachine) {
// If we're in a state where no resolution event will ever occur, we must not add anything to the list.
if ((stateMachine.getState() & (RESOLVED | TERMINAL)) != 0) {
return false;
}
// We use the weak map only for the weak part, not for the map part.
resolvingChannels.put(channel, null);
stateMachine.resolveEvent();
stateMachine.notifyAll();
}
notifyChange();
return true;
}
/**
* {@inheritDoc}
*/
public final void resolve() {
synchronized (stateMachine) {
stateMachine.resolveEvent();
stateMachine.notifyAll();
}
notifyChange();
}
/**
* {@inheritDoc}
*/
public final int getState() {
return stateMachine.getState();
}
/**
* {@inheritDoc}
*/
public Messenger getChannelMessenger(PeerGroupID redirection, String service, String serviceParam) {
// Our transport is always in the same group. If the channel's target group is the same, no group
// redirection is ever needed.
// are we happily resolved ?
return new ThreadedMessengerChannel(getDestinationAddress(), homeGroupID.equals(redirection) ? null : redirection, service,
serviceParam, channelQueueSize, (stateMachine.getState() & (RESOLVED & USABLE)) != 0);
}
/*
* Abstract methods to be provided by implementor. These are fully expected
* to be blocking and may be implemented by invoking transport blocking
* methods, such as EndpointServiceImpl.getLocalTransportMessenger() or
* <em>whateverTransportMessengerWasObtained</em>.sendMessageB(). Should the
* underlying code be non-blocking, these impl methods must simulate it. If
* it's not obvious to do, then this base class is not a good choice.
*/
/**
* Close underlying connection.
*/
protected abstract void closeImpl();
/**
* Make underlying connection.
*
* @return true if successful
*/
protected abstract boolean connectImpl();
/**
* Send a message blocking as needed until the message is sent.
*
* @param msg The message to send.
* @param service The destination service.
* @param param The destination serivce param.
* @throws IOException Thrown for errors encountered while sending the message.
*/
protected abstract void sendMessageBImpl(Message msg, String service, String param) throws IOException;
/**
* Get the logical destination endpoint address.
*
* @return endpoint address
*/
protected abstract EndpointAddress getLogicalDestinationImpl();
}