/** * Copyright 2015 Netflix, 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.netflix.hystrix; import org.junit.Before; import com.netflix.hystrix.HystrixCommand.Setter; import org.junit.Test; import rx.Observable; import rx.Subscriber; import rx.functions.Func0; import rx.schedulers.Schedulers; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.*; public class HystrixTest { @Before public void reset() { Hystrix.reset(); } @Test public void testNotInThread() { assertNull(Hystrix.getCurrentThreadExecutingCommand()); } @Test public void testInsideHystrixThreadViaExecute() { assertNull(Hystrix.getCurrentThreadExecutingCommand()); HystrixCommand<Boolean> command = new HystrixCommand<Boolean>(Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"))) { @Override protected Boolean run() { assertEquals("CommandName", Hystrix.getCurrentThreadExecutingCommand().name()); assertEquals(1, Hystrix.getCommandCount()); return Hystrix.getCurrentThreadExecutingCommand() != null; } }; assertTrue(command.execute()); assertNull(Hystrix.getCurrentThreadExecutingCommand()); assertEquals(0, Hystrix.getCommandCount()); } @Test public void testInsideHystrixThreadViaObserve() { assertNull(Hystrix.getCurrentThreadExecutingCommand()); HystrixCommand<Boolean> command = new HystrixCommand<Boolean>(Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"))) { @Override protected Boolean run() { try { //give the caller thread a chance to check that no thread locals are set on it Thread.sleep(100); } catch (InterruptedException ex) { return false; } assertEquals("CommandName", Hystrix.getCurrentThreadExecutingCommand().name()); assertEquals(1, Hystrix.getCommandCount()); return Hystrix.getCurrentThreadExecutingCommand() != null; } }; final CountDownLatch latch = new CountDownLatch(1); command.observe().subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { latch.countDown(); } @Override public void onError(Throwable e) { fail(e.getMessage()); latch.countDown(); } @Override public void onNext(Boolean value) { System.out.println("OnNext : " + value); assertTrue(value); assertEquals("CommandName", Hystrix.getCurrentThreadExecutingCommand().name()); assertEquals(1, Hystrix.getCommandCount()); } }); try { assertNull(Hystrix.getCurrentThreadExecutingCommand()); assertEquals(0, Hystrix.getCommandCount()); latch.await(); } catch (InterruptedException ex) { fail(ex.getMessage()); } assertNull(Hystrix.getCurrentThreadExecutingCommand()); assertEquals(0, Hystrix.getCommandCount()); } @Test public void testInsideNestedHystrixThread() { HystrixCommand<Boolean> command = new HystrixCommand<Boolean>(Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("OuterCommand"))) { @Override protected Boolean run() { assertEquals("OuterCommand", Hystrix.getCurrentThreadExecutingCommand().name()); System.out.println("Outer Thread : " + Thread.currentThread().getName()); //should be a single execution on this thread assertEquals(1, Hystrix.getCommandCount()); if (Hystrix.getCurrentThreadExecutingCommand() == null) { throw new RuntimeException("BEFORE expected it to run inside a thread"); } HystrixCommand<Boolean> command2 = new HystrixCommand<Boolean>(Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("InnerCommand"))) { @Override protected Boolean run() { assertEquals("InnerCommand", Hystrix.getCurrentThreadExecutingCommand().name()); System.out.println("Inner Thread : " + Thread.currentThread().getName()); //should be a single execution on this thread, since outer/inner are on different threads assertEquals(1, Hystrix.getCommandCount()); return Hystrix.getCurrentThreadExecutingCommand() != null; } }; if (Hystrix.getCurrentThreadExecutingCommand() == null) { throw new RuntimeException("AFTER expected it to run inside a thread"); } return command2.execute(); } }; assertTrue(command.execute()); assertNull(Hystrix.getCurrentThreadExecutingCommand()); } @Test public void testInsideHystrixSemaphoreExecute() { HystrixCommand<Boolean> command = new HystrixCommand<Boolean>(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("SemaphoreIsolatedCommandName")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))) { @Override protected Boolean run() { assertEquals("SemaphoreIsolatedCommandName", Hystrix.getCurrentThreadExecutingCommand().name()); System.out.println("Semaphore Thread : " + Thread.currentThread().getName()); //should be a single execution on the caller thread (since it's busy here) assertEquals(1, Hystrix.getCommandCount()); return Hystrix.getCurrentThreadExecutingCommand() != null; } }; // it should be true for semaphore isolation as well assertTrue(command.execute()); // and then be null again once done assertNull(Hystrix.getCurrentThreadExecutingCommand()); } @Test public void testInsideHystrixSemaphoreQueue() throws Exception { HystrixCommand<Boolean> command = new HystrixCommand<Boolean>(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("SemaphoreIsolatedCommandName")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))) { @Override protected Boolean run() { assertEquals("SemaphoreIsolatedCommandName", Hystrix.getCurrentThreadExecutingCommand().name()); System.out.println("Semaphore Thread : " + Thread.currentThread().getName()); //should be a single execution on the caller thread (since it's busy here) assertEquals(1, Hystrix.getCommandCount()); return Hystrix.getCurrentThreadExecutingCommand() != null; } }; // it should be true for semaphore isolation as well assertTrue(command.queue().get()); // and then be null again once done assertNull(Hystrix.getCurrentThreadExecutingCommand()); } @Test public void testInsideHystrixSemaphoreObserve() throws Exception { HystrixCommand<Boolean> command = new HystrixCommand<Boolean>(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("SemaphoreIsolatedCommandName")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))) { @Override protected Boolean run() { assertEquals("SemaphoreIsolatedCommandName", Hystrix.getCurrentThreadExecutingCommand().name()); System.out.println("Semaphore Thread : " + Thread.currentThread().getName()); //should be a single execution on the caller thread (since it's busy here) assertEquals(1, Hystrix.getCommandCount()); return Hystrix.getCurrentThreadExecutingCommand() != null; } }; // it should be true for semaphore isolation as well assertTrue(command.toObservable().toBlocking().single()); // and then be null again once done assertNull(Hystrix.getCurrentThreadExecutingCommand()); } @Test public void testThreadNestedInsideHystrixSemaphore() { HystrixCommand<Boolean> command = new HystrixCommand<Boolean>(Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("OuterSemaphoreCommand")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))) { @Override protected Boolean run() { assertEquals("OuterSemaphoreCommand", Hystrix.getCurrentThreadExecutingCommand().name()); System.out.println("Outer Semaphore Thread : " + Thread.currentThread().getName()); //should be a single execution on the caller thread assertEquals(1, Hystrix.getCommandCount()); if (Hystrix.getCurrentThreadExecutingCommand() == null) { throw new RuntimeException("BEFORE expected it to run inside a semaphore"); } HystrixCommand<Boolean> command2 = new HystrixCommand<Boolean>(Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestUtil")) .andCommandKey(HystrixCommandKey.Factory.asKey("InnerCommand"))) { @Override protected Boolean run() { assertEquals("InnerCommand", Hystrix.getCurrentThreadExecutingCommand().name()); System.out.println("Inner Thread : " + Thread.currentThread().getName()); //should be a single execution on the thread isolating the second command assertEquals(1, Hystrix.getCommandCount()); return Hystrix.getCurrentThreadExecutingCommand() != null; } }; if (Hystrix.getCurrentThreadExecutingCommand() == null) { throw new RuntimeException("AFTER expected it to run inside a semaphore"); } return command2.execute(); } }; assertTrue(command.execute()); assertNull(Hystrix.getCurrentThreadExecutingCommand()); } @Test public void testSemaphoreIsolatedSynchronousHystrixObservableCommand() { HystrixObservableCommand<Integer> observableCmd = new SynchronousObservableCommand(); assertNull(Hystrix.getCurrentThreadExecutingCommand()); final CountDownLatch latch = new CountDownLatch(1); observableCmd.observe().subscribe(new Subscriber<Integer>() { @Override public void onCompleted() { latch.countDown(); } @Override public void onError(Throwable e) { fail(e.getMessage()); latch.countDown(); } @Override public void onNext(Integer value) { System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " SyncObservable latched Subscriber OnNext : " + value); } }); try { assertNull(Hystrix.getCurrentThreadExecutingCommand()); assertEquals(0, Hystrix.getCommandCount()); latch.await(); } catch (InterruptedException ex) { fail(ex.getMessage()); } assertNull(Hystrix.getCurrentThreadExecutingCommand()); assertEquals(0, Hystrix.getCommandCount()); } // @Test // public void testSemaphoreIsolatedAsynchronousHystrixObservableCommand() { // HystrixObservableCommand<Integer> observableCmd = new AsynchronousObservableCommand(); // // assertNull(Hystrix.getCurrentThreadExecutingCommand()); // // final CountDownLatch latch = new CountDownLatch(1); // // observableCmd.observe().subscribe(new Subscriber<Integer>() { // @Override // public void onCompleted() { // latch.countDown(); // } // // @Override // public void onError(Throwable e) { // fail(e.getMessage()); // latch.countDown(); // } // // @Override // public void onNext(Integer value) { // System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " AsyncObservable latched Subscriber OnNext : " + value); // } // }); // // try { // assertNull(Hystrix.getCurrentThreadExecutingCommand()); // assertEquals(0, Hystrix.getCommandCount()); // latch.await(); // } catch (InterruptedException ex) { // fail(ex.getMessage()); // } // // assertNull(Hystrix.getCurrentThreadExecutingCommand()); // assertEquals(0, Hystrix.getCommandCount()); // } @Test public void testMultipleSemaphoreObservableCommandsInFlight() throws InterruptedException { int NUM_COMMANDS = 50; List<Observable<Integer>> commands = new ArrayList<Observable<Integer>>(); for (int i = 0; i < NUM_COMMANDS; i++) { commands.add(Observable.defer(new Func0<Observable<Integer>>() { @Override public Observable<Integer> call() { return new AsynchronousObservableCommand().observe(); } })); } final AtomicBoolean exceptionFound = new AtomicBoolean(false); final CountDownLatch latch = new CountDownLatch(1); Observable.merge(commands).subscribe(new Subscriber<Integer>() { @Override public void onCompleted() { System.out.println("OnCompleted"); latch.countDown(); } @Override public void onError(Throwable e) { System.out.println("OnError : " + e); e.printStackTrace(); exceptionFound.set(true); latch.countDown(); } @Override public void onNext(Integer n) { System.out.println("OnNext : " + n + " : " + Thread.currentThread().getName() + " : " + Hystrix.getCommandCount());// + " : " + Hystrix.getCurrentThreadExecutingCommand().name() + " : " + Hystrix.getCommandCount()); } }); latch.await(); assertFalse(exceptionFound.get()); } //see https://github.com/Netflix/Hystrix/issues/280 @Test public void testResetCommandProperties() { HystrixCommand<Boolean> cmd1 = new ResettableCommand(100, 1, 10); assertEquals(100L, (long) cmd1.getProperties().executionTimeoutInMilliseconds().get()); assertEquals(1L, (long) cmd1.getProperties().executionIsolationSemaphoreMaxConcurrentRequests().get()); //assertEquals(10L, (long) cmd1.threadPool.getExecutor()..getCorePoolSize()); Hystrix.reset(); HystrixCommand<Boolean> cmd2 = new ResettableCommand(700, 2, 40); assertEquals(700L, (long) cmd2.getProperties().executionTimeoutInMilliseconds().get()); assertEquals(2L, (long) cmd2.getProperties().executionIsolationSemaphoreMaxConcurrentRequests().get()); //assertEquals(40L, (long) cmd2.threadPool.getExecutor().getCorePoolSize()); } private static class SynchronousObservableCommand extends HystrixObservableCommand<Integer> { protected SynchronousObservableCommand() { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")) .andCommandKey(HystrixCommandKey.Factory.asKey("SyncObservable")) .andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(1000)) ); } @Override protected Observable<Integer> construct() { return Observable.create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { try { System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " SyncCommand construct()"); assertEquals("SyncObservable", Hystrix.getCurrentThreadExecutingCommand().name()); assertEquals(1, Hystrix.getCommandCount()); Thread.sleep(10); System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " SyncCommand construct() -> OnNext(1)"); subscriber.onNext(1); Thread.sleep(10); System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " SyncCommand construct() -> OnNext(2)"); subscriber.onNext(2); System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " SyncCommand construct() -> OnCompleted"); subscriber.onCompleted(); } catch (Throwable ex) { subscriber.onError(ex); } } }); } } private static class AsynchronousObservableCommand extends HystrixObservableCommand<Integer> { protected AsynchronousObservableCommand() { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")) .andCommandKey(HystrixCommandKey.Factory.asKey("AsyncObservable")) .andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(1000)) ); } @Override protected Observable<Integer> construct() { return Observable.create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { try { System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " AsyncCommand construct()"); Thread.sleep(10); System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " AsyncCommand construct() -> OnNext(1)"); subscriber.onNext(1); Thread.sleep(10); System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " AsyncCommand construct() -> OnNext(2)"); subscriber.onNext(2); System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " AsyncCommand construct() -> OnCompleted"); subscriber.onCompleted(); } catch (Throwable ex) { subscriber.onError(ex); } } }).subscribeOn(Schedulers.computation()); } } private static class ResettableCommand extends HystrixCommand<Boolean> { ResettableCommand(int timeout, int semaphoreCount, int poolCoreSize) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionTimeoutInMilliseconds(timeout) .withExecutionIsolationSemaphoreMaxConcurrentRequests(semaphoreCount)) .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(poolCoreSize))); } @Override protected Boolean run() throws Exception { return true; } } }