/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.regression.view; import com.espertech.esper.client.*; import com.espertech.esper.supportregression.bean.SupportMarketDataBean; import com.espertech.esper.supportregression.client.SupportConfigFactory; import org.junit.Assert; import junit.framework.TestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.locks.ReentrantLock; /** * Test for N threads feeding events that affect M statements which employ a small time window. * Each of the M statements is associated with a symbol and each event send hits exactly one * statement only. * <p> * Thus the timer is fairly busy when active, competing with N application threads. * Created for ESPER-59 Internal Threading Bugs Found. * <p> * Exceptions can occur in * (1) an application thread during sendEvent() outside of the listener, causes the test to fail * (2) an application thread during sendEvent() inside of the listener, causes assertion to fail * (3) the timer thread, causes an exception to be logged and assertion *may* fail */ public class TestViewTimeWinMultithreaded extends TestCase { private Thread[] threads; private ResultUpdateListener[] listeners; protected void tearDown() throws Exception { listeners = null; } public void testMultithreaded() throws Exception { int numSymbols = 1; int numThreads = 4; int numEventsPerThread = 50000; double timeWindowSize = 0.2; // Set up threads, statements and listeners setUp(numSymbols, numThreads, numEventsPerThread, timeWindowSize); // Start threads long startTime = System.currentTimeMillis(); for (Thread thread : threads) { thread.run(); } // Wait for completion for (Thread thread : threads) { thread.join(); } long endTime = System.currentTimeMillis(); // Check listener results long totalReceived = 0; for (ResultUpdateListener listener : listeners) { totalReceived += listener.getNumReceived(); assertFalse(listener.isCaughtRuntimeException()); } double numTimeWindowAdvancements = (endTime - startTime) / 1000 / timeWindowSize; log.info("Completed, expected=" + numEventsPerThread * numThreads + " numTimeWindowAdvancements=" + numTimeWindowAdvancements + " totalReceived=" + totalReceived); assertTrue(totalReceived < numEventsPerThread * numThreads + numTimeWindowAdvancements + 1); assertTrue(totalReceived >= numEventsPerThread * numThreads); } private void setUp(int numSymbols, int numThreads, int numEvents, double timeWindowSize) { EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider(SupportConfigFactory.getConfiguration()); epService.initialize(); // Create a statement for N number of symbols, each it's own listener String symbols[] = new String[numSymbols]; listeners = new ResultUpdateListener[symbols.length]; for (int i = 0; i < symbols.length; i++) { symbols[i] = "S" + i; String viewExpr = "select symbol, sum(volume) as sumVol " + "from " + SupportMarketDataBean.class.getName() + "(symbol='" + symbols[i] + "')#time(" + timeWindowSize + ")"; EPStatement testStmt = epService.getEPAdministrator().createEPL(viewExpr); listeners[i] = new ResultUpdateListener(); testStmt.addListener(listeners[i]); } // Create threads to send events threads = new Thread[numThreads]; TimeWinRunnable[] runnables = new TimeWinRunnable[threads.length]; ReentrantLock lock = new ReentrantLock(); for (int i = 0; i < threads.length; i++) { runnables[i] = new TimeWinRunnable(i, epService.getEPRuntime(), lock, symbols, numEvents); threads[i] = new Thread(runnables[i]); } } public static class TimeWinRunnable implements Runnable { private final int threadNum; private final EPRuntime epRuntime; private final ReentrantLock sharedLock; private final String[] symbols; private final int numberOfEvents; public TimeWinRunnable(int threadNum, EPRuntime epRuntime, ReentrantLock sharedLock, String[] symbols, int numberOfEvents) { this.threadNum = threadNum; this.epRuntime = epRuntime; this.sharedLock = sharedLock; this.symbols = symbols; this.numberOfEvents = numberOfEvents; } public void run() { for (int i = 0; i < numberOfEvents; i++) { int symbolNum = (threadNum + numberOfEvents) % symbols.length; String symbol = symbols[symbolNum]; long volume = 1; Object theEvent = new SupportMarketDataBean(symbol, -1, volume, null); sharedLock.lock(); try { epRuntime.sendEvent(theEvent); } finally { sharedLock.unlock(); } } } } public static class ResultUpdateListener implements UpdateListener { private boolean isCaughtRuntimeException; private int numReceived = 0; private String lastSymbol = null; public void update(EventBean[] newEvents, EventBean[] oldEvents) { if ((newEvents == null) || (newEvents.length == 0)) { return; } try { numReceived += newEvents.length; String symbol = (String) newEvents[0].get("symbol"); if (lastSymbol != null) { Assert.assertEquals(lastSymbol, symbol); } else { lastSymbol = symbol; } } catch (RuntimeException ex) { log.error("Unexpected exception querying results", ex); isCaughtRuntimeException = true; throw ex; } } public int getNumReceived() { return numReceived; } public boolean isCaughtRuntimeException() { return isCaughtRuntimeException; } } private static final Logger log = LoggerFactory.getLogger(TestViewTimeWinMultithreaded.class); }