package freenet.support; import freenet.node.FastRunnable; import junit.framework.TestCase; public class PrioritizedTickerTest extends TestCase { private WaitableExecutor realExec; private MyTicker ticker; private class MyTicker extends PrioritizedTicker { private boolean sleeping; private Object sleepSync = new Object(); public MyTicker(Executor executor, int portNumber) { super(executor, portNumber); } protected void sleep(long sleepTime) throws InterruptedException { if(sleepTime == MAX_SLEEP_TIME) { synchronized(sleepSync) { sleeping = true; sleepSync.notifyAll(); } } super.sleep(sleepTime); if(sleepTime == MAX_SLEEP_TIME) { synchronized(sleepSync) { sleeping = false; } } } public void waitForSleeping() throws InterruptedException { synchronized(sleepSync) { while(!sleeping) { sleepSync.wait(); } } } public void waitForIdle() throws InterruptedException { // Wait until all jobs have been removed from the queue. while(queuedJobsUniqueTimes() > 0) { waitForSleeping(); } // Wait until the jobs have actually been started off thread or completed on thread. waitForSleeping(); } } @Override protected void setUp() throws Exception { super.setUp(); realExec = new WaitableExecutor(new PooledExecutor()); ticker = new MyTicker(realExec, 0); ticker.start(); } private int runCount = 0; Runnable simpleRunnable = new Runnable() { @Override public void run() { synchronized(PrioritizedTickerTest.this) { runCount++; } } }; Runnable simpleRunnable2 = new Runnable() { @Override public void run() { synchronized(PrioritizedTickerTest.this) { runCount+=10; } } }; private enum BlockTickerJobState { WAITING, BLOCKING, FINISHED } /** Allows us to block the Ticker. Because it's a FastRunnable it will be run directly on * the Ticker thread itself. But it's not actually fast - it waits! */ private class BlockTickerJob implements FastRunnable { private BlockTickerJobState state = BlockTickerJobState.WAITING; private boolean proceed = false; @Override public synchronized void run() { state = BlockTickerJobState.BLOCKING; notifyAll(); while(!proceed) { try { wait(); } catch (InterruptedException e) { // Ignore. } } state = BlockTickerJobState.FINISHED; notifyAll(); } public synchronized void waitForBlocking() throws InterruptedException { while(state != BlockTickerJobState.BLOCKING) { wait(); } } public synchronized void waitForFinished() throws InterruptedException { while(state != BlockTickerJobState.FINISHED) { wait(); } } public synchronized void unblockAndWait() throws InterruptedException { waitForBlocking(); proceed = true; notifyAll(); waitForFinished(); } } public void testSimple() throws InterruptedException { synchronized(PrioritizedTickerTest.this) { runCount = 0; } assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); ticker.queueTimedJob(simpleRunnable, 0); ticker.waitForIdle(); realExec.waitForIdle(); synchronized(PrioritizedTickerTest.this) { assertEquals(runCount, 1); } assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); BlockTickerJob blocker = new BlockTickerJob(); ticker.queueTimedJob(blocker, "Block the ticker", 0, true, false); blocker.waitForBlocking(); ticker.queueTimedJob(simpleRunnable, "test", 0, true, false); assert(ticker.queuedJobs() == 1); blocker.unblockAndWait(); ticker.waitForIdle(); realExec.waitForIdle(); assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); synchronized(PrioritizedTickerTest.this) { assertEquals(runCount, 2); } } public void testRemove() throws InterruptedException { synchronized(PrioritizedTickerTest.this) { runCount = 0; } assert(ticker.queuedJobs() == 0); BlockTickerJob blocker = new BlockTickerJob(); ticker.queueTimedJob(blocker, "Block the ticker", 0, true, false); blocker.waitForBlocking(); ticker.queueTimedJob(simpleRunnable, "test", 0, true, false); assert(ticker.queuedJobs() == 1); assert(ticker.queuedJobsUniqueTimes() == 1); ticker.removeQueuedJob(simpleRunnable); assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); synchronized(PrioritizedTickerTest.this) { assert(runCount == 0); } blocker.unblockAndWait(); } public void testRemoveTwoInSameMillisecond() throws InterruptedException { BlockTickerJob blocker = new BlockTickerJob(); ticker.queueTimedJob(blocker, "Block the ticker", 0, true, false); blocker.waitForBlocking(); // Use absolute time to ensure they are both in the same millisecond. long tRunAt = System.currentTimeMillis(); ticker.queueTimedJobAbsolute(simpleRunnable, "test1", tRunAt, true, false); ticker.queueTimedJobAbsolute(simpleRunnable2, "test2", tRunAt, true, false); assert(ticker.queuedJobs() == 2); int count = ticker.queuedJobsUniqueTimes(); assert(count == 1); ticker.removeQueuedJob(simpleRunnable); assert(ticker.queuedJobs() == 1); assert(ticker.queuedJobsUniqueTimes() == 1); // Remove it again, should not throw or affect other queued job. ticker.removeQueuedJob(simpleRunnable); assert(ticker.queuedJobs() == 1); assert(ticker.queuedJobsUniqueTimes() == 1); // Remove second job. ticker.removeQueuedJob(simpleRunnable2); assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); // Remove second job again, should not throw. ticker.removeQueuedJob(simpleRunnable2); assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); blocker.unblockAndWait(); assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); synchronized(PrioritizedTickerTest.this) { assert(runCount == 0); } } public void testDeduping() throws InterruptedException { synchronized(PrioritizedTickerTest.this) { runCount = 0; } assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); BlockTickerJob blocker = new BlockTickerJob(); ticker.queueTimedJob(blocker, "Block the ticker", 0, true, false); blocker.waitForBlocking(); long runAt = System.currentTimeMillis(); ticker.queueTimedJobAbsolute(simpleRunnable, "De-dupe test", runAt, true, true); assert(ticker.queuedJobs() == 1); assert(ticker.queuedJobsUniqueTimes() == 1); ticker.queueTimedJobAbsolute(simpleRunnable, "De-dupe test", runAt+1, true, true); assert(ticker.queuedJobs() == 1); assert(ticker.queuedJobsUniqueTimes() == 1); blocker.unblockAndWait(); ticker.waitForIdle(); realExec.waitForIdle(); synchronized(PrioritizedTickerTest.this) { assertEquals(runCount, 1); } assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); // Now backwards blocker = new BlockTickerJob(); ticker.queueTimedJob(blocker, "Block the ticker", 0, true, false); blocker.waitForBlocking(); runAt = System.currentTimeMillis(); // Note that these will actually be run on the Ticker, and therefore be de-duped. ticker.queueTimedJobAbsolute(simpleRunnable, "De-dupe test", runAt+1, false, true); assert(ticker.queuedJobs() == 1); assert(ticker.queuedJobsUniqueTimes() == 1); ticker.queueTimedJobAbsolute(simpleRunnable, "De-dupe test", runAt, false, true); assert(ticker.queuedJobs() == 1); assert(ticker.queuedJobsUniqueTimes() == 1); blocker.unblockAndWait(); ticker.waitForIdle(); realExec.waitForIdle(); assert(ticker.queuedJobs() == 0); assert(ticker.queuedJobsUniqueTimes() == 0); synchronized(PrioritizedTickerTest.this) { assertEquals(runCount, 2); } } }