@Test
public void testSemaphorePermitsInUse() {
final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
// this semaphore will be shared across multiple command instances
final TryableSemaphoreActual sharedSemaphore =
new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(3));
// used to wait until all commands have started
final CountDownLatch startLatch = new CountDownLatch(sharedSemaphore.numberOfPermits.get() + 1);
// used to signal that all command can finish
final CountDownLatch sharedLatch = new CountDownLatch(1);
final Runnable sharedSemaphoreRunnable = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() {
public void run() {
try {
new LatchedSemaphoreCommand(circuitBreaker, sharedSemaphore, startLatch, sharedLatch).observe().toBlocking().single();
} catch (Exception e) {
e.printStackTrace();
}
}
});
// creates group of threads each using command sharing a single semaphore
// I create extra threads and commands so that I can verify that some of them fail to obtain a semaphore
final int sharedThreadCount = sharedSemaphore.numberOfPermits.get() * 2;
final Thread[] sharedSemaphoreThreads = new Thread[sharedThreadCount];
for (int i = 0; i < sharedThreadCount; i++) {
sharedSemaphoreThreads[i] = new Thread(sharedSemaphoreRunnable);
}
// creates thread using isolated semaphore
final TryableSemaphoreActual isolatedSemaphore =
new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1));
final CountDownLatch isolatedLatch = new CountDownLatch(1);
// tracks failures to obtain semaphores
final AtomicInteger failureCount = new AtomicInteger();
final Thread isolatedThread = new Thread(new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() {
public void run() {
try {
new LatchedSemaphoreCommand(circuitBreaker, isolatedSemaphore, startLatch, isolatedLatch).observe().toBlocking().single();
} catch (Exception e) {
e.printStackTrace();
failureCount.incrementAndGet();
}
}
}));
// verifies no permits in use before starting threads
assertEquals("wrong number of permits for shared semaphore", 0, sharedSemaphore.getNumberOfPermitsUsed());
assertEquals("wrong number of permits for isolated semaphore", 0, isolatedSemaphore.getNumberOfPermitsUsed());
for (int i = 0; i < sharedThreadCount; i++) {
sharedSemaphoreThreads[i].start();
}
isolatedThread.start();
// waits until all commands have started
try {
startLatch.await(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// verifies that all semaphores are in use
assertEquals("wrong number of permits for shared semaphore",
sharedSemaphore.numberOfPermits.get().longValue(), sharedSemaphore.getNumberOfPermitsUsed());
assertEquals("wrong number of permits for isolated semaphore",
isolatedSemaphore.numberOfPermits.get().longValue(), isolatedSemaphore.getNumberOfPermitsUsed());
// signals commands to finish
sharedLatch.countDown();
isolatedLatch.countDown();
try {
for (int i = 0; i < sharedThreadCount; i++) {
sharedSemaphoreThreads[i].join();
}
isolatedThread.join();
} catch (Exception e) {
e.printStackTrace();
fail("failed waiting on threads");
}
// verifies no permits in use after finishing threads
assertEquals("wrong number of permits for shared semaphore", 0, sharedSemaphore.getNumberOfPermitsUsed());
assertEquals("wrong number of permits for isolated semaphore", 0, isolatedSemaphore.getNumberOfPermitsUsed());
// verifies that some executions failed
final int expectedFailures = sharedSemaphore.getNumberOfPermitsUsed();
assertEquals("failures expected but did not happen", expectedFailures, failureCount.get());
}