/** * 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.OutputStream; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.LockSupport; import org.fudgemsg.FudgeContext; import org.fudgemsg.FudgeMsg; import org.fudgemsg.wire.FudgeDataOutputStreamWriter; import org.fudgemsg.wire.FudgeMsgWriter; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.util.ArgumentChecker; /** * Allows messages being written concurrently to be batched onto a smaller number of threads. * If a thread is already writing messages, the next thread will be blocked and batch all * further messages allowing other threads to complete quickly. This reduces the number of * threads created but blocked (i.e. consuming memory resources) when processing a large * number of incoming requests concurrently. */ public class MessageBatchingWriter { private final Semaphore _writingLock = new Semaphore(0); private FudgeMsgWriter _out; private boolean _writingThreadActive; private boolean _flushRequired; private Queue<FudgeMsg> _messages; private long _nanoFlushDelay; public MessageBatchingWriter() { _out = null; } public MessageBatchingWriter(final FudgeMsgWriter out) { ArgumentChecker.notNull(out, "out"); _out = out; } public void setFlushDelay(final int microseconds) { _nanoFlushDelay = microseconds * 1000; } private static FudgeMsgWriter createFudgeMsgWriter(final FudgeContext fudgeContext, final OutputStream out) { ArgumentChecker.notNull(fudgeContext, "fudgeContext"); ArgumentChecker.notNull(out, "out"); final FudgeDataOutputStreamWriter writer = new FudgeDataOutputStreamWriter(fudgeContext, out); writer.setFlushOnEnvelopeComplete(false); return new FudgeMsgWriter(writer); } public MessageBatchingWriter(final FudgeContext fudgeContext, final OutputStream out) { this(createFudgeMsgWriter(fudgeContext, out)); } public void setFudgeMsgWriter(final FudgeMsgWriter out) { _out = out; } public void setFudgeMsgWriter(final FudgeContext fudgeContext, final OutputStream out) { setFudgeMsgWriter(createFudgeMsgWriter(fudgeContext, out)); } protected FudgeMsgWriter getFudgeMsgWriter() { return _out; } public void write(FudgeMsg message) { Queue<FudgeMsg> messages = null; synchronized (this) { if (_writingThreadActive) { if (_messages != null) { // Another thread is already blocked, so tag onto that and return _messages.add(message); return; } else { // Another thread is already writing messages = new LinkedList<FudgeMsg>(); _messages = messages; } } else { _writingThreadActive = true; } } boolean waitForOtherThreads = false; if (messages == null) { try { beforeWrite(); getFudgeMsgWriter().writeMessage(message); } finally { synchronized (this) { if (_messages != null) { // Another thread is blocking to be the active thread _writingLock.release(); } else { // No other messages have been attempted _writingThreadActive = false; if (_nanoFlushDelay > 0) { if (!_flushRequired) { waitForOtherThreads = true; _flushRequired = true; } } else { final FudgeMsgWriter writer = getFudgeMsgWriter(); if (writer != null) { writer.flush(); } } } } } } else { try { _writingLock.acquire(); } catch (InterruptedException e) { throw new OpenGammaRuntimeException("Interrupted", e); } synchronized (this) { // Stop other threads from queuing onto us _messages = null; } try { beforeWrite(); do { getFudgeMsgWriter().writeMessage(message); message = messages.poll(); } while (message != null); } finally { synchronized (this) { if (_messages != null) { // Another thread is blocking to be the active thread _writingLock.release(); } else { // No other messages have been attempted _writingThreadActive = false; if (_nanoFlushDelay > 0) { if (!_flushRequired) { waitForOtherThreads = true; _flushRequired = true; } } else { getFudgeMsgWriter().flush(); } } } } } // TODO: it would be better if this could be offloaded to another thread so that // we don't block the caller and only have one thread doing the park. if (waitForOtherThreads) { // Can't reliably do a sub-millisecond precision sleep, so use park LockSupport.parkNanos(_nanoFlushDelay); synchronized (this) { if (_flushRequired) { if (!_writingThreadActive) { // No other threads have become active to write data so flush getFudgeMsgWriter().flush(); } _flushRequired = false; } } } } /** * Called before a message (or batch of messages) will be written by this writer. Use this * to e.g. initialize or create the FudgeMsgWriter object. */ protected void beforeWrite() { } }