package com.mysema.luja.impl; import static org.junit.Assert.assertEquals; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.io.FileUtils; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; import com.mysema.luja.LuceneSession; import com.mysema.luja.LuceneSessionFactory; import com.mysema.luja.LuceneTransactional; import com.mysema.luja.LuceneWriter; import com.mysema.luja.QueryTestHelper; import com.mysema.luja.mapping.domain.QMovie; public class ThreadingTest { private LuceneSessionFactoryImpl sessionFactory; private TestDao dao; private static class TestRuntimeException extends RuntimeException { private static final long serialVersionUID = 0L; } private static class Task implements Callable<Throwable> { TestDao dao; String action; Task(TestDao dao, String action) { this.dao = dao; this.action = action; } @Override public Throwable call() throws Exception { try { if (action.equals("read")) { dao.read(); } if (action.equals("write")) { dao.write(); } if (action.equals("reset")) { dao.reset(); } if (action.equals("exception")) { dao.exception(); } return null; } catch (Throwable t) { return t; } } } private static interface TestDao { void write(); void exception(); void reset(); void read(); } private static class TestDaoImpl implements TestDao { private final LuceneSessionFactory sessionFactory; private final QMovie doc = new QMovie("d"); int counter = 1; int readCount = 0; private final Object writeLock = new Object(); private final Object readLock = new Object(); private final Object resetLock = new Object(); TestDaoImpl(LuceneSessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @Override @LuceneTransactional public void write() { LuceneSession session = sessionFactory.getCurrentSession(); LuceneWriter writer = session.beginAppend(); int numOfDocs = 1000; for (int i = 0; i < numOfDocs; i++) { writer.addDocument(QueryTestHelper.createDocument( ""+i, "title " + counter, "", "", counter, 0)); } synchronized (writeLock) { counter += numOfDocs; } System.out.println("Added " + numOfDocs + " documents "); } @Override @LuceneTransactional public void exception() { LuceneSession session = sessionFactory.getCurrentSession(); LuceneWriter writer = session.beginAppend(); writer.addDocument(QueryTestHelper.getDocument()); System.out.println("About to throw RuntimeException"); throw new TestRuntimeException(); } @Override @LuceneTransactional public void reset() { LuceneSession session = sessionFactory.getCurrentSession(); synchronized (resetLock) { counter = 0; } session.beginReset().addDocument( QueryTestHelper.createDocument("1", "title " + counter, "", "", counter, 0)); System.out.println("Resetted index "); } @Override @LuceneTransactional(readOnly = true) public void read() { LuceneSession session = sessionFactory.getCurrentSession(); int count = (int) session.createQuery().where(doc.title.startsWith("title")).count(); synchronized (readLock) { readCount++; if (readCount % 1000 == 0) { System.out.println("Document count is " + count + " after " + readCount + " reads"); } } } } @Before public void before() throws IOException { String path = "target/index"; FileUtils.deleteDirectory(new File(path)); sessionFactory = new LuceneSessionFactoryImpl("target/index"); // Initialize index QueryTestHelper.addData(sessionFactory); AspectJProxyFactory factory = new AspectJProxyFactory(new TestDaoImpl(sessionFactory)); factory.addAspect(new LuceneTransactionHandler()); dao = factory.getProxy(); } private static class TaskRunner implements Callable<List<Throwable>> { private final String action; private final int requestsPerMinute; private final int starDelayInSecs; public boolean stop = false; private final int clients; ScheduledExecutorService threads; TestDao dao; TaskRunner(String action, int clients, int requestsPerMinute, int startDelayInSecs, TestDao dao) { this.action = action; this.clients = clients; this.requestsPerMinute = requestsPerMinute; this.starDelayInSecs = startDelayInSecs; this.dao = dao; } @Override public List<Throwable> call() throws Exception { threads = Executors.newScheduledThreadPool(clients); List<Throwable> errors = new ArrayList<Throwable>(); List<Future<Throwable>> tasks = new ArrayList<Future<Throwable>>(); // Fire tasks for (int i = 0; i < clients; i++) { tasks.add(threads.schedule( new Task(dao, action), i * starDelayInSecs, TimeUnit.SECONDS)); } long startTime = System.currentTimeMillis(); int numOfRequests = 0; // Take results and fire again while (tasks.size() > 0) { int taskReadyIndex = -1; for (int i = 0; i < tasks.size(); i++) { try { tasks.get(i).get(1, TimeUnit.MILLISECONDS); taskReadyIndex = i; break; } catch (TimeoutException e) { // Ignore } } if (taskReadyIndex == -1) { continue; } Throwable error = tasks.get(taskReadyIndex).get(); tasks.remove(taskReadyIndex); numOfRequests++; if (error != null && !(error instanceof TestRuntimeException)) { errors.add(error); } if (!stop) { tasks.add(threads.schedule( new Task(dao, action), 60 / requestsPerMinute, TimeUnit.SECONDS)); } } float elapsedMins = ((float) (System.currentTimeMillis() - startTime) / 1000) / 60; System.out.println(numOfRequests + " " + action + " requests, " + numOfRequests / elapsedMins + " requests/min"); threads.shutdown(); return errors; } } @Test @Ignore public void Simultaneous() throws InterruptedException, ExecutionException { sessionFactory.setDefaultLockTimeout(5000); ExecutorService threads = Executors.newFixedThreadPool(4); int simulationtimeInSecs = 90; // Readers TaskRunner readRunner = new TaskRunner("read", 100, 120, simulationtimeInSecs / 1000, dao); Future<List<Throwable>> reads = threads.submit(readRunner); // Writers TaskRunner writeRunner = new TaskRunner("write", 1, 6, 0, dao); Future<List<Throwable>> writes = threads.submit(writeRunner); // Resetters TaskRunner resetRunner = new TaskRunner("reset", 1, 2, 15, dao); Future<List<Throwable>> resets = threads.submit(resetRunner); //Exceptions TaskRunner exceptionsRunner = new TaskRunner("exception", 2, 3, 10, dao); Future<List<Throwable>> exceptions = threads.submit(exceptionsRunner); Thread.sleep(simulationtimeInSecs * 1000); readRunner.stop = true; writeRunner.stop = true; resetRunner.stop = true; exceptionsRunner.stop = true; List<Throwable> readErrors = reads.get(); List<Throwable> writeErrors = writes.get(); List<Throwable> resetErrors = resets.get(); List<Throwable> exceptionErrors = exceptions.get(); threads.shutdown(); System.out.println("Read errors: " + readErrors.size()); System.out.println("Write errors: " + writeErrors.size()); System.out.println("Reset errors: " + resetErrors.size()); System.out.println("Exceptions errors: " + exceptionErrors.size()); printErrors(readErrors); printErrors(writeErrors); printErrors(resetErrors); printErrors(exceptionErrors); assertEquals("Read errors", 0, readErrors.size()); assertEquals("Write errors", 0, writeErrors.size()); assertEquals("Reset errors", 0, resetErrors.size()); assertEquals("Exception errors", 0, exceptionErrors.size()); // // Map<Class<?>, Integer> errorTypes = new HashMap<Class<?>, Integer>(); // List<Throwable> errors = new ArrayList<Throwable>(); // for (int i = 0; i < runs; i++) { // Throwable t = pool.take().get(); // if (t != null) { // if (!errorTypes.containsKey(t.getClass())) { // errorTypes.put(t.getClass(), 0); // } // errorTypes.put(t.getClass(), errorTypes.get(t.getClass()) + 1); // errors.add(t); // } // } } private void printErrors(List<Throwable> errors) { int count = 0; for (Throwable t : errors) { System.out.println("------------------"); System.out.println(""); t.printStackTrace(System.out); if (count++ > 3) { break; } } } }