package net.jxta.impl.endpoint;
import java.io.IOException;
import java.util.LinkedList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.endpoint.AbstractMessenger;
import net.jxta.endpoint.ChannelMessenger;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.Messenger;
import net.jxta.endpoint.MessengerState;
import net.jxta.peergroup.PeerGroupID;
/**
* Base class for all truly asynchronous messenger implementations, that is, those which
* are event-driven and do not require additional threads to operate. This base class
* provides some common facilities, such as basic non-blocking queueing and messenger
* state management.
*
* @author Iain McGinniss (iain.mcginniss@onedrum.com)
*/
public abstract class AsynchronousMessenger extends AbstractMessenger {
public static final String CLOSED_MESSAGE = "Messenger is closed. It cannot be used to send messages";
private static final Logger LOG = Logger.getLogger(AsynchronousMessenger.class.getName());
private final PeerGroupID homeGroupID;
private final ArrayBlockingQueue<QueuedMessage> sendQueue;
private final AtomicBoolean inputClosed = new AtomicBoolean(false);
private final AtomicBoolean sending = new AtomicBoolean(false);
private final MessengerState stateMachine = new MessengerState(true, distributingListener) {
@Override
protected void closeInputAction() {
LOG.log(Level.FINE, "Closing input for messenger to {0}", getDestinationAddress());
inputClosed.set(true);
}
@Override
protected void closeOutputAction() {
LOG.log(Level.FINE, "Closing output for messenger to {0}", getDestinationAddress());
requestClose();
}
@Override
protected void connectAction() {
// immediately re-signal that the connection is down, to indicate
// that we cannot reconnect
downEvent();
}
@Override
protected void failAllAction() {
LOG.log(Level.FINE, "Marking all queued messages on messenger to {0} as failed", getDestinationAddress());
LinkedList<QueuedMessage> allMessages = new LinkedList<QueuedMessage>();
sendQueue.drainTo(allMessages);
for(QueuedMessage message : allMessages) {
message.getWriteListener().writeFailure(new Exception("Messenger unexpectedly closed"));
}
}
@Override
protected void startAction() {
// TODO: should we attempt to push messages here?
}
};
public AsynchronousMessenger(PeerGroupID homeGroupID, EndpointAddress dest, int messageQueueSize) {
super(dest);
this.homeGroupID = homeGroupID;
// TODO: assess whether this queue should be "fair" or not
this.sendQueue = new ArrayBlockingQueue<QueuedMessage>(messageQueueSize);
}
public final void close() {
closeEvent();
}
public final Messenger getChannelMessenger(PeerGroupID redirection, String service, String serviceParam) {
return new ChannelMessenger(getDestinationAddress(), homeGroupID.equals(redirection) ? null : redirection, service, serviceParam) {
public void close() {
AsynchronousMessenger.this.close();
}
public EndpointAddress getLogicalDestinationAddress() {
EndpointAddress rawLogical = AsynchronousMessenger.this.getLogicalDestinationAddress();
if (rawLogical == null) {
return null;
}
return new EndpointAddress(rawLogical, origService, origServiceParam);
}
public int getState() {
return AsynchronousMessenger.this.getState();
}
public void resolve() {
AsynchronousMessenger.this.resolve();
}
public void sendMessageB(Message msg, String service, String serviceParam) throws IOException {
String effectiveService = effectiveService(service);
String effectiveParam = effectiveParam(service, serviceParam);
AsynchronousMessenger.this.sendMessageB(msg, effectiveService, effectiveParam);
}
public boolean sendMessageN(Message msg, String service, String serviceParam) {
String effectiveService = effectiveService(service);
String effectiveParam = effectiveParam(service, serviceParam);
return AsynchronousMessenger.this.sendMessageN(msg, effectiveService, effectiveParam);
}
};
}
public final int getState() {
return stateMachine.getState();
}
public final void resolve() {
// asynchronous messengers are assumed to be resolved from creation
}
public final void sendMessageB(Message msg, String service, String serviceParam) throws IOException {
if(inputClosed.get()) {
throw new IOException(CLOSED_MESSAGE);
}
TransportUtils.retargetMessage(msg, service, serviceParam, getLocalAddress(), dstAddress);
AsynchronousMessageWriteListener listener = new AsynchronousMessageWriteListener(msg);
try {
// block on putting the element in the queue.
sendQueue.put(new QueuedMessage(msg, listener));
attemptPushMessages();
long totalWaitTime = 0;
while (listener.isWriteSubmitted() && !listener.await(500, TimeUnit.MILLISECONDS))
{
totalWaitTime = totalWaitTime + 500;
if (totalWaitTime > 60000)
{
throw new IOException("Waited 60 secs for message delivery, giving up, must be an issue with channel");
}
LOG.warning("Waiting for message send for " + totalWaitTime);
}
if(!listener.isWriteSubmitted())
{
throw new IOException("Failed to write message");
}
if(!listener.wasSuccessful()) {
IOException failedWrite = new IOException("Failed to write message");
failedWrite.initCause(listener.getFailureCause());
throw failedWrite;
}
} catch(InterruptedException e) {
IOException failure = new IOException("Interrupted while synchronously sending message");
failure.initCause(e);
throw failure;
}
}
public final boolean sendMessageN(Message msg, String service, String serviceParam) {
if(inputClosed.get()) {
TransportUtils.markMessageWithSendFailure(msg, new IOException(CLOSED_MESSAGE));
return false;
}
TransportUtils.retargetMessage(msg, service, serviceParam, getLocalAddress(), dstAddress);
if(sendQueue.offer(new QueuedMessage(msg, new AsynchronousMessageWriteListener(msg)))) {
msgsEvent();
attemptPushMessages();
return !TransportUtils.isMarkedWithFailure(msg);
} else {
saturatedEvent();
TransportUtils.markMessageWithOverflowFailure(msg);
return false;
}
}
/**
* Will attempt to pass any queued messages in {@link #sendQueue} to the subclass'
* implementation of {@link #sendMessageImpl}. This is guaranteed to only be done
* by one thread at a time, if subsequent threads try to send messages while another
* is doing this the method will simply return without doing anything.
*/
private void attemptPushMessages() {
boolean sendSucceeding = true;
while(ableToSend() && sendSucceeding) {
if(sendQueue.size() == 0) {
idleEvent();
return;
}
if(!sending.compareAndSet(false, true)) {
// nothing to send, or another thread is already sending
return;
}
try {
SendStatus status = pushSingleMessage();
switch(status) {
case SATURATED:
case FAIL:
sendSucceeding = false;
break;
}
if(inputClosed.get() && sendQueue.isEmpty()) {
// this is our prompt to close the connection gracefully
requestClose();
}
} finally {
sending.set(false);
}
}
}
private boolean ableToSend() {
int sendableState = Messenger.CONNECTED
| Messenger.SENDING
| Messenger.SENDINGSATURATED
| Messenger.CLOSING;
return (getState() & sendableState) != 0;
}
private enum SendStatus {
SUCCESS,
SATURATED,
FAIL,
QUEUE_EMPTY
}
private SendStatus pushSingleMessage() {
// Strategy designed to avoid losing messages:
// tenatively peek at the queue and attempt to send the message
// if the real implementation accepts sending this message at this
// time, then remove it from the queue.
QueuedMessage message = sendQueue.peek();
if(message != null) {
if(sendMessageImpl(message)) {
sendQueue.poll();
msgsEvent();
return SendStatus.SUCCESS;
} else if(TransportUtils.isMarkedWithFailure(message.getMessage())) {
sendQueue.poll();
return SendStatus.FAIL;
} else {
return SendStatus.SATURATED;
}
}
return SendStatus.QUEUE_EMPTY;
}
private void closeEvent() {
synchronized(stateMachine) {
stateMachine.closeEvent();
}
}
private void downEvent() {
synchronized(stateMachine) {
stateMachine.downEvent();
}
}
private void idleEvent() {
synchronized(stateMachine) {
stateMachine.idleEvent();
}
}
private void msgsEvent() {
synchronized(stateMachine) {
stateMachine.msgsEvent();
}
}
private void saturatedEvent() {
synchronized(stateMachine) {
stateMachine.saturatedEvent();
}
}
/* ************ METHODS RELATED TO CHILD IMPLEMENTATIONS ********************* */
/**
* It is intended that subclasses will invoke this method when space become available
* on the write buffer for more messages. This will push any queued messages to the
* child implementation via the {@link #sendMessageImpl(Message)} method, until it
* indicates that the write buffer is full again.
*/
protected final void pullMessages() {
attemptPushMessages();
}
/**
* It is intended that subclasses will invoke this method when the underlying transport
* is disconnected or fails unexpectedly.
*/
protected final void connectionFailed() {
downEvent();
}
/**
* It is intended that subclasses will invoke this method when the underlying transport
* has been closed gracefully in response to a call to {@link #requestClose()}.
*/
protected final void connectionCloseComplete() {
synchronized(stateMachine) {
if(stateMachine.getState() == Messenger.CLOSING) {
// during a graceful close, all messages are sent and then a close is requested.
// at this point, the implementation has finished closing so we indicate
// that we are idle, signalling the end of the graceful close.
idleEvent();
} else {
// otherwise, this is a close in response to some sort of panic event,
// and we simply signal the connection is down.
downEvent();
}
}
}
/*
* IMPLEMENTER RESPONSIBILITIES BELOW THIS POINT:
*/
/**
* Attempts to write the message to the underlying network endpoint, if there is buffer space
* available. Implementations of this method MUST NOT BLOCK for any substantial amount of time.
* @returns null if there is insufficient buffer space available to write the message.
* @returns a MessengerWriteFuture instance if the message was successfully transferred to the
* write buffer. This can later be checked for completion, when the message has been successfully
* flushed to the network.
*/
protected abstract boolean sendMessageImpl(QueuedMessage message);
protected abstract EndpointAddress getLocalAddress();
public abstract EndpointAddress getLogicalDestinationAddress();
/**
* Requests that the underlying transport used for this messenger be closed and disposed.
* Implementations of this method MUST be asynchronous, and call {@link #connectionCloseComplete()}
* once the transport is closed.
* Implementations of this method MUST NOT BLOCK for any substantial amount of time.
*/
protected abstract void requestClose();
}