Java tutorial
/* Copyright (C) 2013-2014 Ian Teune <ian.teune@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.tinspx.util.concurrent; import static com.google.common.base.Preconditions.*; import com.google.common.base.Throwables; import com.google.common.base.Ticker; import com.google.common.collect.Range; import com.google.common.util.concurrent.Uninterruptibles; import com.tinspx.util.base.NumberUtils; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import lombok.ToString; import lombok.experimental.Builder; import org.apache.commons.lang3.mutable.MutableInt; import static org.junit.Assert.*; import org.junit.Test; /** * Tests {@link DelayedSemaphore}. * <p> * Most tests take a very long time to run and are therefore not run. * {@link #testAll()} tests nearly every feature. * * @author Ian */ public class DelayedSemaphoreTest { private final Object lock = new Object(); private long last; private int tests; private volatile long attempts; private static long testCount; public DelayedSemaphoreTest() { } /** * Test of create method, of class DelayedSemaphore. */ @Test @SuppressWarnings("ResultOfObjectAllocationIgnored") public void testInit() throws InterruptedException { new DelayedSemaphore(1, 0, null); DelayedSemaphore.create(1, 0); DelayedSemaphore.create(1, 1); DelayedSemaphore ls = DelayedSemaphore.create(5, 50000000); assertEquals(5, ls.permits()); assertEquals(5, ls.availablePermits()); assertEquals(50000000, ls.delay()); assertSame(Ticker.systemTicker(), ls.ticker()); ls.acquire(); //4 Thread.sleep(10); assertEquals(5, ls.permits()); assertEquals(4, ls.availablePermits()); ls.acquire();//3 ls.acquire();//2 ls.release();//3 assertEquals(2, ls.availablePermits()); Thread.sleep(51); assertEquals(3, ls.availablePermits()); assertEquals(5, ls.permits()); ls.release(); //4 ls.release();//5 try { ls.release(); fail(); } catch (IllegalStateException ex) { } assertTrue(ls.tryAcquire(-1, TimeUnit.DAYS)); assertTrue(ls.tryAcquire(1, -1, TimeUnit.DAYS)); Thread.sleep(51); assertTrue(ls.tryAcquire(2, -1, TimeUnit.DAYS)); assertEquals(1, ls.availablePermits(0)); try { ls.release(5); fail(); } catch (IllegalArgumentException ex) { } ls.release(4); assertEquals(5, ls.availablePermits(0)); assertEquals(0, ls.availablePermits(TimeUnit.HOURS.toNanos(1))); //acquire invalid args try { ls.acquire(-1L); fail(); } catch (IllegalArgumentException ex) { } try { ls.acquire(0); fail(); } catch (IllegalArgumentException ex) { } try { ls.acquire(-1); fail(); } catch (IllegalArgumentException ex) { } try { ls.acquire(0, 10); fail(); } catch (IllegalArgumentException ex) { } try { ls.acquire(-1, 10); fail(); } catch (IllegalArgumentException ex) { } try { ls.acquire(1, -1); fail(); } catch (IllegalArgumentException ex) { } try { ls.acquire(2, -1); fail(); } catch (IllegalArgumentException ex) { } //tryAcquire no timeout, invalid args try { ls.tryAcquire(-1L); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(0); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(-1); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(0, 10); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(-1, 10); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(1, -1); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(2, -1); fail(); } catch (IllegalArgumentException ex) { } //tryAcquire with timeout, invalid arguments try { ls.tryAcquire(-1L, 10, TimeUnit.NANOSECONDS); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(0, 10, TimeUnit.NANOSECONDS); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(-1, 10, TimeUnit.NANOSECONDS); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(0, 10, 10, TimeUnit.NANOSECONDS); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(-1, 10, 10, TimeUnit.NANOSECONDS); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(1, -1, 10, TimeUnit.NANOSECONDS); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(2, -1, 10, TimeUnit.NANOSECONDS); fail(); } catch (IllegalArgumentException ex) { } try { ls.tryAcquire(0L, 10, null); fail(); } catch (NullPointerException ex) { } try { ls.tryAcquire(1, 10, null); fail(); } catch (NullPointerException ex) { } try { ls.tryAcquire(2, 10, null); fail(); } catch (NullPointerException ex) { } try { ls.tryAcquire(1, 0, 10, null); fail(); } catch (NullPointerException ex) { } try { ls.tryAcquire(2, 0, 10, null); fail(); } catch (NullPointerException ex) { } DelayedSemaphore.create(1, 0); try { DelayedSemaphore.create(0, 1); fail(); } catch (IllegalArgumentException ex) { } try { DelayedSemaphore.create(1, -1); fail(); } catch (IllegalArgumentException ex) { } try { DelayedSemaphore.create(1, 0, null); fail(); } catch (NullPointerException ex) { } } static class AssertListener implements DelayedSemaphore.Listener { final DelayedSemaphore ds; int acquire; int release; int releaseExpect = 1; int acquireExpect = 1; public AssertListener(DelayedSemaphore ds) { this.ds = ds; } @Override public void onAcquire(DelayedSemaphore ls, int permitsAcquired) { assertSame(ls, ds); assertEquals(acquireExpect, permitsAcquired); acquire++; } @Override public void onRelease(DelayedSemaphore ls, int permitsReleased) { assertSame(ls, ds); assertEquals(releaseExpect, permitsReleased); release++; } public void assertAcquire(int a) { assertEquals(a, acquire); } public void assertRelease(int r) { assertEquals(r, release); } } @Test public void testListeners() throws InterruptedException { DelayedSemaphore ds = DelayedSemaphore.create(3, 100000000); //100 ms AssertListener al = new AssertListener(ds); AssertListener el = new AssertListener(ds); ThreadUtilsTest.AssertExecutor ex = new ThreadUtilsTest.AssertExecutor(); ds.addListener(al); assertEquals(1, ds.listenerCount()); ds.addListener(el, ex); assertEquals(2, ds.listenerCount()); ds.removeListener(el); assertEquals(1, ds.listenerCount()); ds.removeListener(al); assertEquals(0, ds.listenerCount()); ds.addListener(al); ds.addListener(el, ex); assertEquals(2, ds.listenerCount()); ds.acquire(); //2 al.assertAcquire(1); el.assertAcquire(1); ex.assertExactly(1); assertTrue(ds.tryAcquire()); //1 al.assertAcquire(2); el.assertAcquire(2); ex.assertExactly(2); ds.release(); //2 al.assertRelease(1); el.assertRelease(1); ex.assertExactly(3); ds.release(); //3 al.assertRelease(2); el.assertRelease(2); ex.assertExactly(4); try { ds.release(); fail(); } catch (IllegalStateException e) { } al.assertRelease(2); el.assertRelease(2); ex.assertExactly(4); assertEquals(0, ds.estimatedWait()); ds.acquire(); //2 long est = ds.estimatedWait(); // System.out.println(est); assertTrue(est + "", est > 90000000); //90 ms assertTrue(est + "", est < 100000000); //100 ms long time = System.nanoTime(); ds.acquire(); //1 ds.acquire(); //0 time = System.nanoTime() - time; assertTrue(time + "", time > 90000000); //180 ms assertTrue(time + "", time < 110000000); //100 ms // System.out.println("time: " + time); assertEquals(-1, ds.estimatedWait()); al.assertAcquire(5); el.assertAcquire(5); ex.assertExactly(7); try { ds.release(4); fail(); } catch (IllegalArgumentException e) { } al.releaseExpect = 3; al.acquireExpect = 3; el.releaseExpect = 3; el.acquireExpect = 3; ds.release(3); al.assertRelease(3); el.assertRelease(3); ex.assertExactly(8); try { ds.acquire(4); fail(); } catch (IllegalArgumentException e) { } ds.acquire(3); al.assertAcquire(6); el.assertAcquire(6); ex.assertExactly(9); } private static final int THREADS = 8; private static final int TESTS = 20; // @Test public void testSingleVary() throws InterruptedException { testSingle(false); } // @Test public void testSingleAcquire() throws InterruptedException { testSingle(true); } public void testSingle(boolean onlyAcquire) throws InterruptedException { Ticker ticker = Ticker.systemTicker(); final long delay = TimeUnit.MILLISECONDS.toNanos(50); DelayedSemaphore ls = new DelayedSemaphore(1, delay, ticker); SingleRunner testers[] = new SingleRunner[THREADS]; Thread threads[] = new Thread[THREADS]; tests = 0; for (int i = 0; i < THREADS; i++) { testers[i] = new SingleRunner(ls, ticker, delay, TESTS, onlyAcquire); threads[i] = new Thread(ThreadUtils.suppress(testers[i])); threads[i].start(); } for (int i = 0; i < THREADS; i++) { threads[i].join(); } for (int i = 0; i < THREADS; i++) { if (testers[i].fail != null) { fail(testers[i].fail); } } assertEquals(tests, THREADS * TESTS); if (!onlyAcquire) { System.out.println("single: " + attempts); } } private String acquire(DelayedSemaphore ls, int count) throws InterruptedException { final long MAX_WAIT = TimeUnit.MICROSECONDS.toNanos(50000), MAX_CHECK = TimeUnit.MICROSECONDS.toNanos(75000); switch (count % 3) { case 0: ls.acquire(); return null; case 1: while (!ls.tryAcquire()) { } return null; case 2: while (true) { long time = ls.ticker().read(); boolean acquired = ls.tryAcquire(MAX_WAIT, TimeUnit.NANOSECONDS); time = ls.ticker().read() - time; if (time > MAX_CHECK) { return String.format("%d > %d (max %d), acquired: %b", time, MAX_CHECK, MAX_WAIT, acquired); } // System.out.println("passed"); if (acquired) { return null; } else { attempts++; } } default: throw new AssertionError(); } } private class SingleRunner implements Runnable { final Ticker ticker; final long delay; final int tests; final DelayedSemaphore ls; String fail; final boolean onlyAcquire; public SingleRunner(DelayedSemaphore ls, Ticker ticker, long delay, int tests, boolean onlyAcquire) { checkArgument(delay >= 0); checkArgument(tests > 0); this.delay = delay; this.tests = tests; this.ls = checkNotNull(ls); this.ticker = checkNotNull(ticker); this.onlyAcquire = onlyAcquire; } @Override public void run() { try { runTests(); } catch (InterruptedException ex) { fail = Throwables.getStackTraceAsString(ex); } } private void runTests() throws InterruptedException { for (int i = 0; i < tests; i++) { if (onlyAcquire) { ls.acquire(); } else { fail = acquire(ls, i); if (fail != null) { return; } } synchronized (lock) { final long now = ticker.read(); DelayedSemaphoreTest.this.tests++; if (now - last < delay) { fail = (now - last) + " < " + delay; return; } else { last = now; } } ls.release(); } } } private static final int PERMITS = 4; // @Test public void testMultipleVary() throws InterruptedException { testMultiple(false); } // @Test public void testMultipleAcquire() throws InterruptedException { testMultiple(true); } public void testMultiple(boolean onlyAcquire) throws InterruptedException { Ticker ticker = Ticker.systemTicker(); final long delay = TimeUnit.MILLISECONDS.toNanos(100); DelayedSemaphore ls = new DelayedSemaphore(PERMITS, delay, ticker); int th = THREADS; int t = TESTS * 3; MultipleRunner testers[] = new MultipleRunner[th]; Thread threads[] = new Thread[th]; tests = 0; long times[] = new long[PERMITS]; for (int i = 0; i < th; i++) { testers[i] = new MultipleRunner(ls, ticker, delay, PERMITS, t, times, onlyAcquire); threads[i] = new Thread(ThreadUtils.suppress(testers[i])); threads[i].start(); } for (int i = 0; i < th; i++) { threads[i].join(); } for (int i = 0; i < th; i++) { if (testers[i].fail != null) { System.out.println(testers[i].fail); fail(testers[i].fail); } } assertEquals(tests, th * t); if (!onlyAcquire) { System.out.println("multiple: " + attempts); } } private class MultipleRunner implements Runnable { final Ticker ticker; final long delay; final int tests; final int permits; final DelayedSemaphore ls; String fail; final long times[]; final boolean onlyAcquire; public MultipleRunner(DelayedSemaphore ls, Ticker ticker, long delay, int permits, int tests, long times[], boolean onlyAcquire) { checkArgument(delay >= 0); checkArgument(tests > 0); checkArgument(permits > 0); this.permits = permits; this.delay = delay; this.tests = tests; this.ls = checkNotNull(ls); this.ticker = checkNotNull(ticker); this.times = checkNotNull(times); this.onlyAcquire = onlyAcquire; } @Override public void run() { try { runTests(); } catch (InterruptedException ex) { fail = Throwables.getStackTraceAsString(ex); } } private void runTests() throws InterruptedException { for (int i = 0; i < tests; i++) { if (onlyAcquire) { ls.acquire(); } else { fail = acquire(ls, i); if (fail != null) { return; } } synchronized (lock) { final long now = ticker.read(); DelayedSemaphoreTest.this.tests++; final long last = shiftAndGet(times); times[times.length - 1] = now; if (now - last < delay - TimeUnit.MILLISECONDS.toNanos(0)) { fail = (now - last) + " < " + delay; return; } } ls.release(); } } } private static long shiftAndGet(long values[]) { long r = values[0]; for (int i = 1; i < values.length; i++) { values[i - 1] = values[i]; } return r; } private static long[] shiftAndGet(long values[], int permits, long fill) { assert permits > 0 : permits; assert permits <= values.length; long[] out = new long[permits]; System.arraycopy(values, 0, out, 0, permits); System.arraycopy(values, permits, values, 0, values.length - permits); Arrays.fill(values, values.length - permits, values.length, fill); return out; } @SuppressWarnings("UnnecessaryUnboxing") static void runTest(Executor executor, DelayedSemaphore ds, Ticker ticker, int threadCount, int acquisitions, Acquire acquire, Permits permits, Range<Integer> acquireRange, Release release, DelayConstraint constraint) throws InterruptedException { checkArgument(threadCount > 0); DelayTest.DelayTestBuilder builder = DelayTest.builder(); builder.stop(new AtomicBoolean()); builder.start(new CountDownLatch(threadCount)); builder.lock(new ReentrantLock()); builder.releaseTimes(new long[ds.permits()]); builder.acquisitions(acquisitions); builder.ticker(ticker).ds(ds); builder.acquire(acquire).permits(permits).permits(permits).acquireRange(acquireRange); builder.release(release); builder.delayConstraint(constraint); builder.tests(new MutableInt()); builder.totalThreads(threadCount); DelayTest[] testers = new DelayTest[threadCount]; for (int i = 0; i < threadCount; i++) { testers[i] = builder.thread(i).build(); executor.execute(testers[i]); } for (int i = 0; i < threadCount; i++) { testers[i].complete.await(); } String errorMsg = null; for (int i = 0; i < threadCount; i++) { if (testers[i].fail != null) { errorMsg = testers[i].fail; System.out.println(errorMsg); System.out.println(); } } if (errorMsg != null) { fail(errorMsg); } assertEquals(threadCount * acquisitions, builder.tests.getValue().intValue()); if (++testCount % 10 == 0) { System.out.printf("%d, Tests: %s\n", testCount, builder.tests); } } @ToString static class DelayTest implements Runnable { final CountDownLatch complete = new CountDownLatch(1); final int totalThreads; AtomicBoolean stop; String fail; boolean started; final CountDownLatch start; final int thread; final Lock lock; final long[] releaseTimes; final int acquisitions; final Ticker ticker; final DelayedSemaphore ds; final Acquire acquire; final Permits permits; final Range<Integer> acquireRange; final Release release; final DelayConstraint delayConstraint; final MutableInt tests; @Builder public DelayTest(int totalThreads, CountDownLatch start, int thread, Lock lock, long[] releaseTimes, int acquisitions, Ticker ticker, DelayedSemaphore ds, Acquire acquire, Permits permits, Range<Integer> acquireRange, Release release, DelayConstraint delayConstraint, MutableInt tests, AtomicBoolean stop) { this.totalThreads = totalThreads; this.start = checkNotNull(start); this.thread = thread; this.lock = checkNotNull(lock); this.releaseTimes = checkNotNull(releaseTimes); checkArgument(acquisitions > 0); this.acquisitions = acquisitions; this.ticker = checkNotNull(ticker); this.ds = checkNotNull(ds); this.acquire = checkNotNull(acquire); this.permits = checkNotNull(permits); this.acquireRange = checkNotNull(acquireRange); this.release = checkNotNull(release); this.delayConstraint = checkNotNull(delayConstraint); this.tests = checkNotNull(tests); this.stop = checkNotNull(stop); } synchronized void checkStart() { checkState(!started); started = true; } @Override public void run() { try { tryRun(); } catch (Throwable t) { fail = this + " \n" + Throwables.getStackTraceAsString(t); stop.set(true); } finally { complete.countDown(); } } public void tryRun() { checkStart(); start.countDown(); Uninterruptibles.awaitUninterruptibly(start); for (int i = 0; i < acquisitions; i++) { final int p = permits.permits(ds, acquireRange); try { while (!acquire.acquire(ds, p)) { //continue until acquired } } catch (InterruptedException ex) { throw Throwables.propagate(ex); } try { final long acquireTime = ticker.read(); long[] times; lock.lock(); try { tests.increment(); times = shiftAndGet(releaseTimes, p, acquireTime); } finally { lock.unlock(); } for (long time : times) { if (!delayConstraint.isSatisfied(ds, acquireTime - time)) { fail = String.format("DS: %s\np: %s, acquireTime: %d, time: %d, elapsed: %d\nthis: %s", ds, p, acquireTime, time, acquireTime - time, this); stop.set(true); break; } } if (stop.get()) { return; } } finally { release.release(ds, p); } } } } interface Acquire { boolean acquire(DelayedSemaphore ds, int permits) throws InterruptedException; } interface Release { void release(DelayedSemaphore ds, int permits); } interface Permits { int permits(DelayedSemaphore ds, Range<Integer> acquireRange); } interface DelayConstraint { boolean isSatisfied(DelayedSemaphore ds, long elapsed); } static final Acquire ACQUIRE_INCREMENT = new Acquire() { final AtomicLong counter = new AtomicLong(); @Override public boolean acquire(DelayedSemaphore ds, int permits) throws InterruptedException { switch ((int) (counter.incrementAndGet() % 3)) { case 0: return ACQUIRE_TRY_IMMEDIATE.acquire(ds, permits); case 1: return ACQUIRE_TRY_TIMEOUT.acquire(ds, permits); case 2: return ACQUIRE.acquire(ds, permits); default: throw new AssertionError(); } } @Override public String toString() { return "ACQUIRE_INCREMENT"; } }; static final Acquire ACQUIRE_TRY_IMMEDIATE = new Acquire() { final AtomicLong counter = new AtomicLong(); @Override public boolean acquire(DelayedSemaphore ds, int permits) { if (permits == 1 && counter.incrementAndGet() % 2 == 0) { return ds.tryAcquire(); } else { return ds.tryAcquire(permits); } } @Override public String toString() { return "ACQUIRE_TRY_IMMEDIATE"; } }; static final Acquire ACQUIRE_TRY_TIMEOUT = new Acquire() { final AtomicLong delayCounter = new AtomicLong(); final AtomicLong counter = new AtomicLong(); @Override public boolean acquire(DelayedSemaphore ds, int permits) throws InterruptedException { long timeout = delayCounter.incrementAndGet() % 5; switch ((int) timeout) { case 0: timeout = ds.delay() * 2; break; case 1: timeout = ds.delay() * 3; break; case 2: timeout = ds.delay(); break; case 3: timeout = ds.delay() / 2; break; case 4: timeout = ds.delay() / 3; break; default: throw new AssertionError(timeout); } long time = ds.ticker().read(); boolean result; if (permits == 1 && counter.incrementAndGet() % 2 == 0) { result = ds.tryAcquire(timeout, TimeUnit.NANOSECONDS); } else { result = ds.tryAcquire(permits, timeout, TimeUnit.NANOSECONDS); } time = ds.ticker().read() - time; if (time > timeout + 30000000) { //30ms wiggle room fail(String.format("elapsed: %d, timeout: %d, result: %b, permits: %d, ds: %s", time, timeout, result, permits, ds)); } return result; } @Override public String toString() { return "ACQUIRE_TRY_TIMEOUT"; } }; static final Acquire ACQUIRE = new Acquire() { final AtomicLong counter = new AtomicLong(); @Override public boolean acquire(DelayedSemaphore ds, int permits) throws InterruptedException { if (permits == 1 && counter.incrementAndGet() % 2 == 0) { ds.acquire(); } else { ds.acquire(permits); } return true; } @Override public String toString() { return "ACQUIRE"; } }; static final Release RELEASE_INCREMENT = new Release() { final AtomicLong counter = new AtomicLong(); @Override public void release(DelayedSemaphore ds, int permits) { if (counter.incrementAndGet() % 2 == 0) { RELEASE_LOOP.release(ds, permits); } else { RELEASE.release(ds, permits); } } @Override public String toString() { return "RELEASE_INCREMENT"; } }; static final Release RELEASE_LOOP = new Release() { @Override public void release(DelayedSemaphore ds, int permits) { for (int i = 0; i < permits; i++) { ds.release(); } } @Override public String toString() { return "RELEASE_LOOP"; } }; static final Release RELEASE = new Release() { final AtomicLong counter = new AtomicLong(); @Override public void release(DelayedSemaphore ds, int permits) { if (permits == 1 && counter.incrementAndGet() % 2 == 0) { ds.release(); } else { ds.release(permits); } } @Override public String toString() { return "RELEASE"; } }; static final Permits PERMITS_ALL = new Permits() { @Override public int permits(DelayedSemaphore ds, Range<Integer> acquireRange) { return ds.permits(); } @Override public String toString() { return "PERMITS_ALL"; } }; static final Permits PERMITS_ONE = new Permits() { @Override public int permits(DelayedSemaphore ds, Range<Integer> acquireRange) { return 1; } @Override public String toString() { return "PERMITS_ONE"; } }; static final Permits PERMITS_RANDOM = new Permits() { final Random random = new Random(); @Override public int permits(DelayedSemaphore ds, Range<Integer> acquireRange) { return NumberUtils.getRandomInt(acquireRange, random); } @Override public String toString() { return "PERMITS_RANDOM"; } }; static final Permits PERMITS_INCREMENT = new Permits() { final AtomicLong counter = new AtomicLong(); @Override public int permits(DelayedSemaphore ds, Range<Integer> acquireRange) { switch ((int) (counter.incrementAndGet() % 3)) { case 0: return PERMITS_ALL.permits(ds, acquireRange); case 1: return PERMITS_ONE.permits(ds, acquireRange); case 2: return PERMITS_RANDOM.permits(ds, acquireRange); default: throw new AssertionError(); } } @Override public String toString() { return "PERMITS_INCREMENT"; } }; static Permits constant(int permits) { return new PermitConstant(permits); } @ToString final static class PermitConstant implements Permits { final int permits; public PermitConstant(int permits) { checkArgument(permits > 0); this.permits = permits; } @Override public int permits(DelayedSemaphore ds, Range<Integer> acquireRange) { return permits; } } static final DelayConstraint DELAY_STRICT = new DelayConstraint() { @Override public boolean isSatisfied(DelayedSemaphore ds, long elapsed) { return elapsed >= ds.delay(); } @Override public String toString() { return "DELAY_STRICT"; } }; @ToString final static class PercentDelay implements DelayConstraint { final double percent; public PercentDelay(double percent) { checkArgument(percent > 0); this.percent = percent; } @Override public boolean isSatisfied(DelayedSemaphore ds, long elapsed) { return elapsed >= (ds.delay() - (ds.delay() * percent)); } } @ToString final static class RangeDelay implements DelayConstraint { final long range; public RangeDelay(long range) { checkArgument(range > 0); this.range = range; } @Override public boolean isSatisfied(DelayedSemaphore ds, long elapsed) { assert range < ds.delay(); return elapsed >= (ds.delay() - range); } } static DelayConstraint percent(double percent) { return new PercentDelay(percent); } static DelayConstraint range(long range) { return new RangeDelay(range); } static final List<Acquire> ACQUIRES = Arrays.asList(ACQUIRE, ACQUIRE_TRY_IMMEDIATE, ACQUIRE_TRY_TIMEOUT, ACQUIRE_INCREMENT); static final List<Release> RELEASES = Arrays.asList(RELEASE, RELEASE_LOOP, RELEASE_INCREMENT); static final List<Permits> PERMITS_LIST = Arrays.asList(PERMITS_ONE, PERMITS_ALL, PERMITS_RANDOM, PERMITS_INCREMENT); // static final List<DelayConstraint> CONSTRAINTS = Arrays.asList(DELAY_STRICT); //static void runTest(DelayedSemaphore ds, Ticker ticker, int threadCount, int acquisitions, Acquire acquire, Permits permits, Range<Integer> acquireRange, Release release, DelayConstraint constraint) throws InterruptedException { /** * This method tests every reasonable variation of permits, delay, threads, * and acquire/release methods. It is not normally run as it takes a very * long time to run (about 2.5 hours on my computer). */ // @Test public void testAll() throws InterruptedException { System.out.println( "Total Tests: " + (10 * 4 * 6 * 6 * ACQUIRES.size() * RELEASES.size() * PERMITS_LIST.size())); final Executor executor = Executors.newFixedThreadPool(8, ThreadUtils.daemonThreadFactory()); final int A = 10; // for(int delay = 20000000; delay <= 100000000; delay += 40000000) { for (int delay : Arrays.asList(20000000, 50000000, 100000000)) { // for(int threads = 3; threads <= 8; threads++) { for (int threads : Arrays.asList(1, 2, 3, 4, 6, 8)) { for (int limit = 1; limit <= 6 && limit <= threads + 3; limit++) { for (Acquire acquire : ACQUIRES) { for (Release release : RELEASES) { for (Permits permits : PERMITS_LIST) { runTest(executor, DelayedSemaphore.create(limit, delay), Ticker.systemTicker(), threads, A, acquire, permits, Range.closed(1, limit), //entire permit range release, range(3000000)) //3ms wiggle room ; } //permits } //releases } //acquires } //limit } //threads } //delay } }