package freenet.support; import junit.framework.TestCase; import freenet.support.io.NativeThread; public class MemoryLimitedJobRunnerTest extends TestCase { final Executor executor = new PooledExecutor(); class SynchronousJob extends MemoryLimitedJob { private boolean canStart; private boolean isStarted; private boolean canFinish; private boolean isFinished; private final Object completionSemaphore; SynchronousJob(long size, boolean canStart, Object semaphore) { super(size); this.canStart = canStart; canFinish = false; completionSemaphore = semaphore; } @Override public int getPriority() { return NativeThread.NORM_PRIORITY; } @Override public boolean start(MemoryLimitedChunk chunk) { MemoryLimitedJobRunner runner = chunk.getRunner(); checkRunner(runner); waitForCanStart(); checkRunner(runner); synchronized(completionSemaphore) { isStarted = true; completionSemaphore.notifyAll(); } checkRunner(runner); waitForCanFinish(); checkRunner(runner); synchronized(completionSemaphore) { isFinished = true; completionSemaphore.notifyAll(); } checkRunner(runner); return true; } private synchronized void waitForCanFinish() { while(!canFinish) try { wait(); } catch (InterruptedException e) { // Ignore. } } private synchronized void waitForCanStart() { while(!canStart) try { wait(); } catch (InterruptedException e) { // Ignore. } } public boolean isFinished() { synchronized(completionSemaphore) { return isFinished; } } public boolean isStarted() { synchronized(completionSemaphore) { return isStarted; } } public synchronized void setCanStart() { if(canStart) throw new IllegalStateException(); canStart = true; notify(); } public synchronized void setCanFinish() { if(canFinish) throw new IllegalStateException(); canFinish = true; notify(); } } public void testQueueingSmallDelayed() throws InterruptedException { innerTestQueueingSmallDelayed(1, 10, 20, false); innerTestQueueingSmallDelayed(1, 1024, 1024*2, false); } public void testQueueingManySmallDelayed() throws InterruptedException { innerTestQueueingSmallDelayed(1, 10, 10, false); innerTestQueueingSmallDelayed(1, 20, 10, false); innerTestQueueingSmallDelayed(1, 1024, 1024, false); innerTestQueueingSmallDelayed(1, 2048, 1024, false); innerTestQueueingSmallDelayed(1, 20, 1, false); } private void innerTestQueueingSmallDelayed(int JOB_SIZE, int JOB_COUNT, int JOB_LIMIT, boolean startLive) throws InterruptedException { SynchronousJob[] jobs = new SynchronousJob[JOB_COUNT]; final Object completion = new Object(); for(int i=0;i<jobs.length;i++) jobs[i] = new SynchronousJob(JOB_SIZE, startLive, completion); // If it all fits, run all the jobs at once. If some are going to be queued, use a small thread limit. int maxThreads = JOB_COUNT <= JOB_LIMIT ? JOB_COUNT : 10; MemoryLimitedJobRunner runner = new MemoryLimitedJobRunner(JOB_LIMIT, maxThreads, executor, NativeThread.JAVA_PRIORITY_RANGE); for(SynchronousJob job : jobs) runner.queueJob(job); Thread.sleep(100); assertTrue(noneFinished(jobs)); if(!startLive) { for(SynchronousJob job : jobs) job.setCanStart(); } if(JOB_COUNT <= JOB_LIMIT) waitForAllStarted(jobs, completion); for(SynchronousJob job : jobs) job.setCanFinish(); waitForAllFinished(jobs, completion); waitForZero(runner); } private void waitForZero(MemoryLimitedJobRunner runner) { while(runner.used() > 0) { try { Thread.sleep(1); } catch (InterruptedException e) { // Ignore. } } assertEquals(runner.used(), 0); } // FIXME start the executor immediately. private void waitForAllFinished(SynchronousJob[] jobs, Object semaphore) { synchronized(semaphore) { while(true) { if(allFinished(jobs)) return; try { semaphore.wait(); } catch (InterruptedException e) { // Ignore. } } } } private void waitForAllStarted(SynchronousJob[] jobs, Object semaphore) { synchronized(semaphore) { while(true) { if(allStarted(jobs)) return; try { semaphore.wait(); } catch (InterruptedException e) { // Ignore. } } } } private boolean allFinished(SynchronousJob[] jobs) { for(SynchronousJob job : jobs) { if(!job.isFinished()) return false; } return true; } private boolean allStarted(SynchronousJob[] jobs) { for(SynchronousJob job : jobs) { if(!job.isStarted()) return false; } return true; } private boolean noneFinished(SynchronousJob[] jobs) { for(SynchronousJob job : jobs) { if(job.isFinished()) return false; } return true; } class AsynchronousJob extends MemoryLimitedJob { private boolean canStart; private boolean isStarted; private boolean canFinish; private boolean isFinished; private final Object completionSemaphore; AsynchronousJob(long size, boolean canStart, Object semaphore) { super(size); this.canStart = canStart; canFinish = false; completionSemaphore = semaphore; } @Override public int getPriority() { return NativeThread.NORM_PRIORITY; } @Override public boolean start(final MemoryLimitedChunk chunk) { final MemoryLimitedJobRunner runner = chunk.getRunner(); checkRunner(runner); waitForCanStart(); checkRunner(runner); synchronized(completionSemaphore) { isStarted = true; completionSemaphore.notifyAll(); } checkRunner(runner); Thread t = new Thread(new Runnable() { @Override public void run() { checkRunner(runner); waitForCanFinish(); checkRunner(runner); synchronized(completionSemaphore) { isFinished = true; completionSemaphore.notifyAll(); } checkRunner(runner); assertEquals(chunk.release(), initialAllocation); assertEquals(chunk.release(), 0); checkRunner(runner); } }); t.start(); return false; } private synchronized void waitForCanFinish() { while(!canFinish) try { wait(); } catch (InterruptedException e) { // Ignore. } } private synchronized void waitForCanStart() { while(!canStart) try { wait(); } catch (InterruptedException e) { // Ignore. } } public boolean isFinished() { synchronized(completionSemaphore) { return isFinished; } } public boolean isStarted() { synchronized(completionSemaphore) { return isStarted; } } public synchronized void setCanStart() { if(canStart) throw new IllegalStateException(); canStart = true; notify(); } public synchronized void setCanFinish() { if(canFinish) throw new IllegalStateException(); canFinish = true; notify(); } } public void testAsyncQueueingSmallDelayed() throws InterruptedException { innerTestAsyncQueueingSmallDelayed(1, 10, 20, false); innerTestAsyncQueueingSmallDelayed(1, 1024, 1024*2, false); } public void testAsyncQueueingManySmallDelayed() throws InterruptedException { innerTestAsyncQueueingSmallDelayed(1, 10, 10, false); innerTestAsyncQueueingSmallDelayed(1, 20, 10, false); innerTestAsyncQueueingSmallDelayed(1, 1024, 1024, false); innerTestAsyncQueueingSmallDelayed(1, 2048, 1024, false); innerTestAsyncQueueingSmallDelayed(1, 20, 1, false); } private void innerTestAsyncQueueingSmallDelayed(int JOB_SIZE, int JOB_COUNT, int JOB_LIMIT, boolean startLive) throws InterruptedException { SynchronousJob[] jobs = new SynchronousJob[JOB_COUNT]; final Object completion = new Object(); for(int i=0;i<jobs.length;i++) jobs[i] = new SynchronousJob(JOB_SIZE, startLive, completion); // If it all fits, run all the jobs at once. If some are going to be queued, use a small thread limit. int maxThreads = JOB_COUNT <= JOB_LIMIT ? JOB_COUNT : 10; MemoryLimitedJobRunner runner = new MemoryLimitedJobRunner(JOB_LIMIT, maxThreads, executor, NativeThread.JAVA_PRIORITY_RANGE); for(SynchronousJob job : jobs) runner.queueJob(job); Thread.sleep(100); assertTrue(noneFinished(jobs)); if(!startLive) { for(SynchronousJob job : jobs) job.setCanStart(); } if(JOB_COUNT <= JOB_LIMIT) waitForAllStarted(jobs, completion); for(SynchronousJob job : jobs) job.setCanFinish(); waitForAllFinished(jobs, completion); waitForZero(runner); } protected void checkRunner(MemoryLimitedJobRunner runner) { long used = runner.used(); assertTrue(used <= runner.capacity); assertTrue(used >= 0); } }