package org.threadly.concurrent.wrapper; import static org.junit.Assert.*; import static org.threadly.TestConstants.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.threadly.ThreadlyTestUtil; import org.threadly.concurrent.DoNothingRunnable; import org.threadly.concurrent.PriorityScheduler; import org.threadly.concurrent.StrictPriorityScheduler; import org.threadly.concurrent.TestCallable; import org.threadly.concurrent.lock.StripedLock; import org.threadly.concurrent.wrapper.KeyDistributedExecutorTest.KDCallable; import org.threadly.concurrent.wrapper.KeyDistributedExecutorTest.KDRunnable; import org.threadly.concurrent.wrapper.KeyDistributedExecutorTest.ThreadContainer; import org.threadly.test.concurrent.TestRunnable; import org.threadly.test.concurrent.TestableScheduler; @SuppressWarnings("javadoc") public class KeyDistributedSchedulerTest { private static final int PARALLEL_LEVEL = Runtime.getRuntime().availableProcessors(); private static final int RUNNABLE_COUNT_PER_LEVEL = TEST_QTY; @BeforeClass public static void setupClass() { ThreadlyTestUtil.setIgnoreExceptionHandler(); } private PriorityScheduler scheduler; private Object agentLock; private KeyDistributedScheduler distributor; @Before public void setup() { scheduler = new StrictPriorityScheduler(PARALLEL_LEVEL * 2); StripedLock sLock = new StripedLock(1); agentLock = sLock.getLock(null); // there should be only one lock distributor = new KeyDistributedScheduler(scheduler, sLock, Integer.MAX_VALUE, false); } @After public void cleanup() { scheduler.shutdownNow(); scheduler = null; agentLock = null; distributor = null; } private List<KDRunnable> populate(AddHandler ah) { final List<KDRunnable> runs = new ArrayList<>(PARALLEL_LEVEL * RUNNABLE_COUNT_PER_LEVEL); // hold agent lock to prevent execution till ready synchronized (agentLock) { for (int i = 0; i < PARALLEL_LEVEL; i++) { ThreadContainer tc = new ThreadContainer(); KDRunnable previous = null; for (int j = 0; j < RUNNABLE_COUNT_PER_LEVEL; j++) { KDRunnable tr = new KDRunnable(tc, previous); runs.add(tr); ah.addTDRunnable(tc, tr); previous = tr; } } } return runs; } @Test @SuppressWarnings("unused") public void constructorFail() { try { new KeyDistributedScheduler(1, null); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { new KeyDistributedScheduler(scheduler, null, Integer.MAX_VALUE, false); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } } @Test @SuppressWarnings("unused") public void constructorTest() { // none should throw exception new KeyDistributedScheduler(scheduler); new KeyDistributedScheduler(scheduler, true); new KeyDistributedScheduler(scheduler, 1); new KeyDistributedScheduler(scheduler, 1, true); new KeyDistributedScheduler(1, scheduler); new KeyDistributedScheduler(1, scheduler, true); new KeyDistributedScheduler(1, scheduler, 1); new KeyDistributedScheduler(1, scheduler, 1, true); StripedLock sLock = new StripedLock(1); new KeyDistributedScheduler(scheduler, sLock, 1, false); } @Test (expected = IllegalArgumentException.class) public void getSchedulerForKeyFail() { distributor.getSchedulerForKey(null); } @Test public void getExecutorTest() { assertTrue(scheduler == distributor.getExecutor()); } @Test public void executeTest() { List<KDRunnable> runs = populate(new AddHandler() { @Override public void addTDRunnable(Object key, KDRunnable tdr) { distributor.execute(key, tdr); } }); Iterator<KDRunnable> it = runs.iterator(); while (it.hasNext()) { KDRunnable tr = it.next(); tr.blockTillFinished(1000); assertEquals(1, tr.getRunCount()); // verify each only ran once assertTrue(tr.threadTracker.threadConsistent()); // verify that all threads for a given key ran in the same thread assertTrue(tr.previousRanFirst()); // verify runnables were run in order } } @Test public void executeFail() { try { distributor.execute(null, DoNothingRunnable.instance()); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.execute(new Object(), null); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void submitRunnableFail() { try { distributor.submit(null, DoNothingRunnable.instance()); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.submit(new Object(), null, null); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void submitCallableConsistentThreadTest() { List<KDCallable> runs = new ArrayList<>(PARALLEL_LEVEL * RUNNABLE_COUNT_PER_LEVEL); // hold agent lock to avoid execution till all are submitted synchronized (agentLock) { for (int i = 0; i < PARALLEL_LEVEL; i++) { ThreadContainer tc = new ThreadContainer(); KDCallable previous = null; for (int j = 0; j < RUNNABLE_COUNT_PER_LEVEL; j++) { KDCallable tr = new KDCallable(tc, previous); runs.add(tr); distributor.submit(tc, tr); previous = tr; } } } Iterator<KDCallable> it = runs.iterator(); while (it.hasNext()) { KDCallable tr = it.next(); tr.blockTillFinished(10_000); assertTrue(tr.threadTracker.threadConsistent()); // verify that all threads for a given key ran in the same thread assertTrue(tr.previousRanFirst()); // verify runnables were run in order } } @Test public void submitCallableFail() { try { distributor.submit(null, new TestCallable()); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.submit(new Object(), (Callable<Void>)null); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void scheduleExecutionTest() { List<KDRunnable> runs = populate(new AddHandler() { @Override public void addTDRunnable(Object key, KDRunnable tdr) { distributor.schedule(key, tdr, DELAY_TIME); } }); Iterator<KDRunnable> it = runs.iterator(); while (it.hasNext()) { KDRunnable tr = it.next(); tr.blockTillFinished(1000); assertEquals(1, tr.getRunCount()); // verify each only ran once assertTrue(tr.getDelayTillFirstRun() >= DELAY_TIME); } } @Test public void scheduleExecutionFail() { try { distributor.schedule(new Object(), null, 1000); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.schedule(new Object(), DoNothingRunnable.instance(), -1); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.schedule(null, DoNothingRunnable.instance(), 100); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void submitScheduledRunnableFail() { try { distributor.submitScheduled(new Object(), (Runnable)null, 1000); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.submitScheduled(new Object(), DoNothingRunnable.instance(), -1); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.submitScheduled(null, DoNothingRunnable.instance(), 100); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void submitScheduledCallableFail() { try { distributor.submitScheduled(new Object(), (Callable<?>)null, 1000); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.submitScheduled(new Object(), new TestCallable(), -1); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.submitScheduled(null, new TestCallable(), 100); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void recurringExecutionTest() { List<KDRunnable> runs = populate(new AddHandler() { int initialDelay = 0; @Override public void addTDRunnable(Object key, KDRunnable tdr) { distributor.scheduleTaskWithFixedDelay(key, tdr, initialDelay++, DELAY_TIME); } }); Iterator<KDRunnable> it = runs.iterator(); while (it.hasNext()) { KDRunnable tr = it.next(); assertTrue(tr.getDelayTillRun(2) - tr.getDelayTillRun(1) >= DELAY_TIME); tr.blockTillFinished(10_000, 4); assertFalse(tr.ranConcurrently()); // verify that it never run in parallel } } @Test public void recurringExecutionFail() { try { distributor.scheduleTaskWithFixedDelay(new Object(), null, 1000, 100); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.scheduleTaskWithFixedDelay(new Object(), DoNothingRunnable.instance(), -1, 100); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.scheduleTaskWithFixedDelay(new Object(), DoNothingRunnable.instance(), 100, -1); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } try { distributor.scheduleTaskWithFixedDelay(null, DoNothingRunnable.instance(), 100, 100); fail("Exception should have been thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void removeRunnableTest() { TestableScheduler scheduler = new TestableScheduler(); KeyDistributedScheduler distributor = new KeyDistributedScheduler(scheduler); TestRunnable scheduleRunnable = new TestRunnable(); TestRunnable submitScheduledRunnable = new TestRunnable(); TestRunnable scheduleWithFixedDelayRunnable = new TestRunnable(); distributor.schedule(scheduleRunnable, scheduleRunnable, 10); distributor.submitScheduled(submitScheduledRunnable, submitScheduledRunnable, 10); distributor.scheduleTaskWithFixedDelay(scheduleWithFixedDelayRunnable, scheduleWithFixedDelayRunnable, 10, 10); assertTrue(scheduler.remove(scheduleRunnable)); assertTrue(scheduler.remove(submitScheduledRunnable)); assertTrue(scheduler.remove(scheduleWithFixedDelayRunnable)); } @Test public void removeCallableTest() { TestableScheduler scheduler = new TestableScheduler(); KeyDistributedScheduler distributor = new KeyDistributedScheduler(scheduler); TestCallable submitScheduledCallable = new TestCallable(); distributor.submitScheduled(submitScheduledCallable, submitScheduledCallable, 10); assertTrue(scheduler.remove(submitScheduledCallable)); } private interface AddHandler { public void addTDRunnable(Object key, KDRunnable tdr); } }