package org.infinispan.executors; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import org.infinispan.test.AbstractInfinispanTest; import org.infinispan.util.concurrent.WithinThreadExecutor; import org.testng.annotations.AfterClass; import org.testng.annotations.Test; /** * Basic tests for {@link LimitedExecutor} * * @author Dan Berindei * @since 9.0 */ @Test(groups = "functional", testName = "executors.LimitedExecutorTest") public class LimitedExecutorTest extends AbstractInfinispanTest { public static final String NAME = "Test"; private final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, MILLISECONDS, new SynchronousQueue<>(), getTestThreadFactory(NAME)); @AfterClass(alwaysRun = true) public void stopExecutors() { executor.shutdownNow(); } public void testBasicWithinThread() throws Exception { LimitedExecutor limitedExecutor = new LimitedExecutor(NAME, new WithinThreadExecutor(), 1); CompletableFuture<String> cf = new CompletableFuture<>(); limitedExecutor.execute(() -> cf.complete("value")); assertEquals("value", cf.getNow("task did not run synchronously")); } /** * Test that no more than 1 task runs at a time. */ public void testConcurrencyLimit() throws Exception { eventuallyEquals(0, executor::getActiveCount); LimitedExecutor limitedExecutor = new LimitedExecutor(NAME, executor, 1); CompletableFuture<String> blocker1 = new CompletableFuture<>(); CompletableFuture<String> cf1 = new CompletableFuture<>(); limitedExecutor.execute(() -> { try { cf1.complete(blocker1.get(10, SECONDS)); } catch (Exception e) { cf1.completeExceptionally(e); } }); verifyTaskIsBlocked(limitedExecutor, blocker1, cf1); } /** * Test that an async task ({@code executeAsync()}) will block another task from running * until its {@code CompletableFuture} is completed. */ public void testConcurrencyLimitExecuteAsync() throws Exception { eventuallyEquals(0, executor::getActiveCount); LimitedExecutor limitedExecutor = new LimitedExecutor(NAME, executor, 1); CompletableFuture<String> blocker1 = new CompletableFuture<>(); CompletableFuture<String> cf1 = new CompletableFuture<>(); limitedExecutor.executeAsync(() -> blocker1.thenAccept(cf1::complete)); verifyTaskIsBlocked(limitedExecutor, blocker1, cf1); } /** * Test that no more than 1 task runs at a time when using a {@link WithinThreadExecutor}. */ public void testConcurrencyLimitWithinThread() throws Exception { LimitedExecutor limitedExecutor = new LimitedExecutor(NAME, new WithinThreadExecutor(), 1); CompletableFuture<String> blocker1 = new CompletableFuture<>(); CompletableFuture<String> blocker2 = new CompletableFuture<>(); CompletableFuture<String> cf1 = new CompletableFuture<>(); // execute() will block Future<?> fork1 = fork(() -> { limitedExecutor.execute(() -> { blocker2.complete("blocking"); try { cf1.complete(blocker1.get(10, SECONDS)); } catch (Exception e) { cf1.completeExceptionally(e); } }); }); assertEquals("blocking", blocker2.get(10, SECONDS)); verifyTaskIsBlocked(limitedExecutor, blocker1, cf1); fork1.get(10, SECONDS); } /** * Test that an async task ({@code executeAsync()}) will block another task from running * until its {@code CompletableFuture} is completed, when using a {@link WithinThreadExecutor}. */ public void testConcurrencyLimitExecuteAsyncWithinThread() throws Exception { LimitedExecutor limitedExecutor = new LimitedExecutor(NAME, new WithinThreadExecutor(), 1); CompletableFuture<String> blocker1 = new CompletableFuture<>(); CompletableFuture<String> cf1 = new CompletableFuture<>(); // executeAsync() will not block limitedExecutor.executeAsync(() -> blocker1.thenAccept(cf1::complete)); verifyTaskIsBlocked(limitedExecutor, blocker1, cf1); } private void verifyTaskIsBlocked(LimitedExecutor limitedExecutor, CompletableFuture<String> blocker1, CompletableFuture<String> cf1) throws Exception { CompletableFuture<String> blocker2 = new CompletableFuture<>(); CompletableFuture<String> cf2 = new CompletableFuture<>(); // execute() may block Future<?> fork2 = fork(() -> { limitedExecutor.execute(() -> { try { cf2.complete(cf1.getNow("task 2 ran too early") + " " + blocker2.get(10, SECONDS)); } catch (Exception e) { cf2.completeExceptionally(e); } }); }); assertFalse(cf1.isDone()); assertFalse(cf2.isDone()); blocker1.complete("value1"); assertEquals("value1", cf1.get(10, SECONDS)); assertFalse(cf2.isDone()); blocker2.complete("value2"); assertEquals("value1 value2", cf2.get(10, SECONDS)); fork2.get(10, SECONDS); eventuallyEquals(0, executor::getActiveCount); } }