/*
* Copyright 2012 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.linkedin.parseq;
import static org.testng.Assert.fail;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* @author Jaroslaw Odzga (jodzga@linkedin.com)
*/
public class TestEngineConcurrentPlans {
private Engine _engine;
private ScheduledExecutorService _scheduler;
@BeforeMethod
public void setUp() throws Exception {
final int numCores = Runtime.getRuntime().availableProcessors();
_scheduler = Executors.newScheduledThreadPool(numCores + 1);
_engine = new EngineBuilder().setTaskExecutor(_scheduler).setTimerScheduler(_scheduler)
.setEngineProperty(Engine.MAX_CONCURRENT_PLANS, 10).build();
}
@AfterMethod
public void tearDown() throws Exception {
_engine.shutdown();
_engine.awaitTermination(50, TimeUnit.MILLISECONDS);
_engine = null;
_scheduler.shutdownNow();
_scheduler = null;
}
@Test
public void testRunWithinCapacity() throws InterruptedException {
final AtomicInteger counter = new AtomicInteger(0);
Task<?>[] tasks = new Task<?>[10];
for (int i = 0; i < 10; i++) {
tasks[i] = Task.action(counter::incrementAndGet);
}
for (int i = 0; i < 10; i++) {
_engine.run(tasks[i]);
}
assertTrue(awaitAll(tasks));
assertEquals(10, counter.get());
}
private boolean awaitAll(Task<?>[] tasks) throws InterruptedException {
for (int i = 0; i < tasks.length; i++) {
if (!tasks[i].await(5, TimeUnit.SECONDS)) {
return false;
}
}
return true;
}
@Test
public void testTryRunWithinCapacity() throws InterruptedException {
final AtomicInteger counter = new AtomicInteger(0);
Task<?>[] tasks = new Task<?>[10];
for (int i = 0; i < 10; i++) {
tasks[i] = Task.action(counter::incrementAndGet);
}
for (int i = 0; i < 10; i++) {
assertTrue(_engine.tryRun(tasks[i]));
}
assertTrue(awaitAll(tasks));
assertEquals(10, counter.get());
}
@Test
public void testBlockingRunWithinCapacity() throws InterruptedException {
final AtomicInteger counter = new AtomicInteger(0);
Task<?>[] tasks = new Task<?>[10];
for (int i = 0; i < 10; i++) {
tasks[i] = Task.action(counter::incrementAndGet);
}
for (int i = 0; i < 10; i++) {
_engine.blockingRun(tasks[i]);
}
assertTrue(awaitAll(tasks));
assertEquals(10, counter.get());
}
@Test
public void testTimeBoundedTryRunWithinCapacity() throws InterruptedException {
final AtomicInteger counter = new AtomicInteger(0);
Task<?>[] tasks = new Task<?>[10];
for (int i = 0; i < 10; i++) {
tasks[i] = Task.action(counter::incrementAndGet);
}
for (int i = 0; i < 10; i++) {
assertTrue(_engine.tryRun(tasks[i], 10, TimeUnit.MILLISECONDS));
}
assertTrue(awaitAll(tasks));
assertEquals(10, counter.get());
}
@Test
public void testRunOverCapacity() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
Task<?>[] tasks = new Task<?>[10];
for (int i = 0; i < 10; i++) {
tasks[i] = Task.action(latch::await);
}
//within capacity
for (int i = 0; i < 10; i++) {
_engine.run(tasks[i]);
}
try {
_engine.run(Task.action(() -> {}));
fail();
} catch (IllegalStateException e) {
//expected
}
//release tasks
latch.countDown();
//wait for tasks to finish
assertTrue(awaitAll(tasks));
//should be unblocked
_engine.run(Task.action(() -> {}));
}
@Test
public void testTryRunOverCapacity() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
Task<?>[] tasks = new Task<?>[10];
for (int i = 0; i < 10; i++) {
tasks[i] = Task.action(latch::await);
}
//within capacity
for (int i = 0; i < 10; i++) {
assertTrue(_engine.tryRun(tasks[i]));
}
assertFalse(_engine.tryRun(Task.action(() -> {})));
//release tasks
latch.countDown();
//wait for tasks to finish
assertTrue(awaitAll(tasks));
//should be unblocked
assertTrue(_engine.tryRun(Task.action(() -> {})));
}
@Test
public void testTimeBoundedTryRunOverCapacity() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
Task<?>[] tasks = new Task<?>[10];
for (int i = 0; i < 10; i++) {
tasks[i] = Task.action(latch::await);
}
//within capacity
for (int i = 0; i < 10; i++) {
assertTrue(_engine.tryRun(tasks[i], 10, TimeUnit.MILLISECONDS));
}
assertFalse(_engine.tryRun(Task.action(() -> {}), 10, TimeUnit.MILLISECONDS));
//release tasks
latch.countDown();
//wait for tasks to finish
assertTrue(awaitAll(tasks));
//should be unblocked
assertTrue(_engine.tryRun(Task.action(() -> {}), 10, TimeUnit.MILLISECONDS));
}
@Test
public void testBlockingRunOverCapacity() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
Task<?>[] tasks = new Task<?>[10];
for (int i = 0; i < 10; i++) {
tasks[i] = Task.action(latch::await);
}
//within capacity
for (int i = 0; i < 10; i++) {
_engine.blockingRun(tasks[i]);
}
final CountDownLatch blockedTaskLatch = new CountDownLatch(1);
final CountDownLatch blockedThreadLatch = new CountDownLatch(1);
new Thread(() -> {
blockedThreadLatch.countDown();
_engine.blockingRun(Task.action(() -> { blockedTaskLatch.countDown(); }));
}).start();
//first wait for a background thread to reach _engine.run()
assertTrue(blockedThreadLatch.await(5, TimeUnit.SECONDS));
//sleep for 200ms to make sure background thread executed _engine.run()
Thread.sleep(200);
//verify background tasks didn't run
assertEquals(1L, blockedTaskLatch.getCount());
//release tasks
latch.countDown();
//background thread should be unblocked and background task should eventually run
assertTrue(blockedTaskLatch.await(5, TimeUnit.SECONDS));
}
}