/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.transport.socket; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.net.Socket; import java.util.concurrent.ExecutorService; import org.fudgemsg.FudgeContext; import org.fudgemsg.FudgeMsg; import org.fudgemsg.FudgeMsgEnvelope; import org.fudgemsg.wire.FudgeMsgReader; import org.fudgemsg.wire.FudgeRuntimeIOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.transport.FudgeConnection; import com.opengamma.transport.FudgeConnectionStateListener; import com.opengamma.transport.FudgeMessageReceiver; import com.opengamma.transport.FudgeMessageSender; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.TerminatableJob; /** * A Socket implementation of FudgeConnection */ public class SocketFudgeConnection extends AbstractSocketProcess implements FudgeConnection { private static final Logger s_logger = LoggerFactory.getLogger(SocketFudgeConnection.class); private final FudgeContext _fudgeContext; private final ExecutorService _executorService; private final MessageBatchingWriter _writer = new MessageBatchingWriter() { /** * Prevents re-entrant calls to startIfNecessary if a message is sent as part of a connection * reset callback. */ private boolean _isSending; @Override protected void beforeWrite() { if (!_isSending) { _isSending = true; try { startIfNecessary(); } catch (OpenGammaRuntimeException e) { if (e.getCause() instanceof IOException) { notifyConnectionFailed((IOException) e.getCause()); // Should we still carry on and throw the exception if the user's been given it as a callback? Maybe allow the connectionFailed callback specify which to rethrow? } throw e; } finally { _isSending = false; } } } }; private FudgeMessageReceiver _receiver; private TerminatableJob _receiverJob; private volatile FudgeConnectionStateListener _stateListener; private final FudgeMessageSender _sender = new FudgeMessageSender() { @Override public FudgeContext getFudgeContext() { return _fudgeContext; } @Override public void send(FudgeMsg message) { try { _writer.write(message); } catch (FudgeRuntimeIOException e) { if (exceptionForcedByClose(e.getCause())) { s_logger.info("Connection terminated - message not sent"); } else { s_logger.warn("I/O exception during send - {} - stopping socket to flush error", e.getCause().getMessage()); stop(); notifyConnectionFailed(e); } throw e; } } }; /** * Creates a connection where received messages are processed inline with socket read operations. * * @param fudgeContext the Fudge context, not null */ public SocketFudgeConnection(final FudgeContext fudgeContext) { ArgumentChecker.notNull(fudgeContext, "fudgeContext"); _fudgeContext = fudgeContext; _executorService = null; } /** * Creates a connection where received messages run out of thread to the socket reader using the given * {@link ExecutorService}. * * @param fudgeContext the Fudge context, not null * @param executorService an executor service to run received messages via, not null */ public SocketFudgeConnection(final FudgeContext fudgeContext, final ExecutorService executorService) { ArgumentChecker.notNull(fudgeContext, "fudgeContext"); ArgumentChecker.notNull(executorService, "executorService"); _fudgeContext = fudgeContext; _executorService = executorService; } /** * Sets a delay before flushing data messages to allow adjacent messages to be coalesced. Only useful if the * message sender is being used concurrently. * * @param microseconds the time to wait before flushing, or {@code 0} to flush immediately after a message (or coalesced group) */ public void setFlushDelay(final int microseconds) { _writer.setFlushDelay(microseconds); } /** * Note that the message sender may be called concurrently. All messages will be sent from a single thread * with others returning immediately. Thus successful completion of a {@link FudgeMessageSender#send} does * not guarantee message arrival or that it has even been (or will be) passed to the transport. * * @return the Fudge message sender component of the connection */ @Override public FudgeMessageSender getFudgeMessageSender() { return _sender; } @Override public void setFudgeMessageReceiver(final FudgeMessageReceiver receiver) { _receiver = receiver; } @Override protected void socketOpened(Socket socket, BufferedOutputStream os, BufferedInputStream is) { final FudgeMsgReader reader = _fudgeContext.createMessageReader(is); _writer.setFudgeMsgWriter(_fudgeContext, os); _receiverJob = new TerminatableJob() { @Override protected void runOneCycle() { final FudgeMsgEnvelope envelope; try { envelope = reader.nextMessageEnvelope(); } catch (FudgeRuntimeIOException e) { if (exceptionForcedByClose(e.getCause())) { s_logger.info("Connection terminated"); } else { s_logger.warn("I/O exception during recv - {} - stopping socket to flush error", e.getCause()); stop(); notifyConnectionFailed(e); } return; } if (envelope == null) { s_logger.info("Nothing available on stream. Terminating connection"); stop(); return; } final FudgeMessageReceiver receiver = _receiver; if (receiver != null) { if (_executorService != null) { _executorService.execute(new Runnable() { @Override public void run() { dispatch(receiver, envelope); } }); } else { dispatch(receiver, envelope); } } } private void dispatch(final FudgeMessageReceiver receiver, final FudgeMsgEnvelope envelope) { try { receiver.messageReceived(_fudgeContext, envelope); } catch (Exception e) { s_logger.warn("Unable to dispatch message to receiver", e); } } }; final Thread thread = new Thread(_receiverJob, "Incoming " + socket.getRemoteSocketAddress()); thread.setDaemon(true); thread.start(); // We don't keep hold of the thread as we're never going to join it; terminating the socket will let cause it to stop, finish and be GCd final FudgeConnectionStateListener stateListener = _stateListener; if (stateListener != null) { stateListener.connectionReset(this); } } @Override protected void socketClosed() { _writer.setFudgeMsgWriter(null); _receiverJob.terminate(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("FudgeConnection to "); sb.append(getInetAddresses()); sb.append(':'); sb.append(getPortNumber()); if (!isRunning()) { sb.append(" (not connected)"); } return sb.toString(); } @Override public void setConnectionStateListener(FudgeConnectionStateListener listener) { _stateListener = listener; } protected void notifyConnectionFailed(Exception e) { final FudgeConnectionStateListener stateListener = _stateListener; if (stateListener != null) { try { stateListener.connectionFailed(SocketFudgeConnection.this, e); } catch (Exception e2) { s_logger.warn("Error notifying state listener of connection failure", e2); } } } }