/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.f1x.store;
import org.f1x.SessionIDBean;
import org.f1x.TestCommon;
import org.f1x.api.FixAcceptorSettings;
import org.f1x.api.FixInitiatorSettings;
import org.f1x.api.FixVersion;
import org.f1x.api.message.MessageBuilder;
import org.f1x.api.message.MessageParser;
import org.f1x.api.message.fields.*;
import org.f1x.api.session.SessionID;
import org.f1x.api.session.SessionStatus;
import org.f1x.v1.FixSessionAcceptor;
import org.f1x.v1.FixSessionInitiator;
import org.f1x.v1.SingleSessionAcceptor;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
public class Test_ConcurrentMessageStore extends TestCommon {
private static final int NUM_CLIENT_THREADS = 16;
private static final int NUM_MESSAGES_PER_CLIENT_THREAD = 1024;
private static final int MAX_MESSAGE_SIZE = 4096;
private static final int PORT = 7890;
private static final String INITIATOR_SENDER_ID = "INITIATOR";
private static final String ACCEPTOR_SENDER_ID = "ACCEPTOR";
private final SessionID ServerSessionID = new SessionIDBean(ACCEPTOR_SENDER_ID, INITIATOR_SENDER_ID);
private final SessionID ClientSessionID = new SessionIDBean(INITIATOR_SENDER_ID, ACCEPTOR_SENDER_ID);
/**
* This test uses simple FIX client and FIX server that have in-memory message store.
* Test uses NUM_CLIENT_THREADS threads to send messages to server concurrently.
* At the end we validate that message stores on client and server match (server stores inbound messages into aux msg store).
*/
@Test(timeout = 120000)
public void concurrentSend() throws InterruptedException, IOException {
final MessageStore sentMessages = new InMemoryMessageStore(1 << 24);
final MessageStore receivedMessages = new InMemoryMessageStore(1 << 24);
final TestServer server = new TestServer(PORT, ServerSessionID, receivedMessages);
final TestClient client = new TestClient(PORT, ClientSessionID, sentMessages);
final Thread acceptorThread = new Thread(server, "EchoServer");
acceptorThread.start();
final Thread initiatorThread = new Thread(client, "EchoClient");
initiatorThread.start();
client.waitForAllMessages();
client.close();
//client.close();
System.out.println("Closing server");
server.close();
// compare sent and received messages
final int comparedPrefixSize = 150;
final byte [] sentMessageBuffer= new byte [MAX_MESSAGE_SIZE];
final byte [] receivedMessageBuffer= new byte [MAX_MESSAGE_SIZE];
for (int i=2; i <= NUM_CLIENT_THREADS *NUM_MESSAGES_PER_CLIENT_THREAD; i++) {
Assert.assertEquals("Sent MsgSeqNum", i, sentMessages.get(i, sentMessageBuffer));
Assert.assertEquals("Received MsgSeqNum", i, receivedMessages.get(i, receivedMessageBuffer));
for(int j=0; j < comparedPrefixSize; j++) {
if (sentMessageBuffer[j] != receivedMessageBuffer[j])
Assert.assertEquals("Sent and Received messages do not match", new String(sentMessageBuffer, 0, comparedPrefixSize), new String(receivedMessageBuffer, 0, comparedPrefixSize));
}
}
}
private static class TestServer extends SingleSessionAcceptor {
public TestServer(int bindPort, SessionID sessionID, final MessageStore testMessageStore) {
super (null, bindPort,
new FixSessionAcceptor(FixVersion.FIX44, sessionID, new FixAcceptorSettings()) {
private MessageBuilder messageBuilder; {
messageBuilder = createMessageBuilder();
}
private final byte [] buffer = new byte[MAX_MESSAGE_SIZE];
@Override
protected void processInboundAppMessage(CharSequence msgType, int msgSeqNum, boolean possDup, MessageParser parser) throws IOException {
messageBuilder.clear();
while(parser.next()) {
messageBuilder.add(parser.getTagNum(), parser.getCharSequenceValue());
}
int length = messageBuilder.output(buffer, 0);
testMessageStore.put(msgSeqNum, buffer, 0, length);
}
}
);
}
}
private static class TestClient extends FixSessionInitiator {
private final ClientSendingThread [] clientThreads = new ClientSendingThread [NUM_CLIENT_THREADS];
private final CountDownLatch activeClientThreads = new CountDownLatch(NUM_CLIENT_THREADS);
public TestClient(int port, SessionID sessionID, MessageStore messageStore) {
super(null, port, FixVersion.FIX44, sessionID, new FixInitiatorSettings());
for (int i = 0; i < NUM_CLIENT_THREADS; i++)
clientThreads [i] = new ClientSendingThread(this, i);
setMessageStore(messageStore);
}
@Override
protected void onSessionStatusChanged(SessionStatus oldStatus, SessionStatus newStatus) {
super.onSessionStatusChanged(oldStatus, newStatus);
if (newStatus == SessionStatus.ApplicationConnected)
for (int i = 0; i < NUM_CLIENT_THREADS; i++)
if ( ! clientThreads[i].isAlive())
clientThreads[i].start();
}
void waitForAllMessages() throws InterruptedException {
activeClientThreads.await();
}
}
private static class ClientSendingThread extends Thread {
private final int id;
private final TestClient client;
private final MessageBuilder mb;
public ClientSendingThread(TestClient client, int id) {
super("Client#"+id);
this.id = id;
this.client = client;
this.mb = client.createMessageBuilder();
}
@Override
public void run() {
for(int i=0; i < NUM_MESSAGES_PER_CLIENT_THREAD; i++) {
sendMessage(i);
}
client.activeClientThreads.countDown();
}
public void sendMessage (int i) {
assert client.getSessionStatus() == SessionStatus.ApplicationConnected;
try {
mb.clear();
mb.setMessageType(MsgType.ORDER_SINGLE);
mb.add(FixTags.Account, id);
mb.add(FixTags.ClOrdID, i);
mb.add(FixTags.HandlInst, HandlInst.AUTOMATED_EXECUTION_ORDER_PRIVATE);
mb.add(FixTags.OrderQty, 1);
mb.add(FixTags.OrdType, OrdType.LIMIT);
mb.add(FixTags.Price, 1.43);
mb.add(FixTags.Side, Side.BUY);
mb.add(FixTags.Symbol, "EUR/USD");
mb.add(FixTags.SecurityType, SecurityType.FOREIGN_EXCHANGE_CONTRACT);
mb.add(FixTags.TimeInForce, TimeInForce.DAY);
mb.add(FixTags.ExDestination, "GS");
mb.addUTCTimestamp(FixTags.TransactTime, System.currentTimeMillis());
client.send(mb);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}