diff --git a/Hystrix-master/.gitignore b/Hystrix-master/.gitignore new file mode 100644 index 0000000..82cbac8 --- /dev/null +++ b/Hystrix-master/.gitignore @@ -0,0 +1,64 @@ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log + +# OS generated files # +###################### +.DS_Store* +ehthumbs.db +Icon? +Thumbs.db + +# Editor Files # +################ +*~ +*.swp + +# Gradle Files # +################ +.gradle +.m2 + +# Build output directies +target/ +build/ + +# IntelliJ specific files/directories +out +.idea +*.ipr +*.iws +*.iml +atlassian-ide-plugin.xml + +# Eclipse specific files/directories +.classpath +.project +.settings +.metadata +bin/ + +# NetBeans specific files/directories +.nbattrs diff --git a/Hystrix-master/.travis.yml b/Hystrix-master/.travis.yml new file mode 100644 index 0000000..542e04c --- /dev/null +++ b/Hystrix-master/.travis.yml @@ -0,0 +1,16 @@ +addons: + apt: + packages: + - lynx + +language: java +sudo: false + +jdk: + - oraclejdk8 + +script: + - ./gradlew --info check + +after_failure: + - if [ -f /home/travis/build/Netflix/Hystrix/hystrix-core/build/reports/tests/test/index.html ]; then lynx -dump /home/travis/build/Netflix/Hystrix/hystrix-core/build/reports/tests/test/index.html; fi diff --git a/Hystrix-master/CHANGELOG.md b/Hystrix-master/CHANGELOG.md new file mode 100644 index 0000000..3488715 --- /dev/null +++ b/Hystrix-master/CHANGELOG.md @@ -0,0 +1,1045 @@ +# Hystrix Releases # + +### Version 1.5.13 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.13%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.13/)) ### + +- [Pull 1621](https://github.com/Netflix/Hystrix/pull/1621) Fixed bug where an unsubscription of a command in half-open state leaves circuit permanently open +- [Pull 1605](https://github.com/Netflix/Hystrix/pull/1605) Change return value on unsubscribe to Observable.empty(). Thanks @atoulme ! +- [Pull 1615](https://github.com/Netflix/Hystrix/pull/1615) Updated Gradle to version 4.0. Thanks @wlsc ! +- [Pull 1616](https://github.com/Netflix/Hystrix/pull/1616) Javanica: Wrong hystrix event type for fallback missing. Thanks @dmgcodevil ! +- [Pull 1606](https://github.com/Netflix/Hystrix/pull/1606) Escape user entered input to avoid HTML injection. This fixes #1456. Thanks @atoulme ! +- [Pull 1595](https://github.com/Netflix/Hystrix/pull/1595) Possibility to add custom root node for command and thread pool metrics. Thanks @dstoklosa ! +- [Pull 1587](https://github.com/Netflix/Hystrix/pull/1587) Throw IllegalStateException if request cache is not available when clearing. Thanks @jack-kerouac ! + +### Version 1.5.12 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.12%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.12/)) ### + +- [Pull 1586](https://github.com/Netflix/Hystrix/pull/1586) Start streams for CodaHale metric consumer, ot get it actually working +- [Pull 1584](https://github.com/Netflix/Hystrix/pull/1584) Javanica: Wire up allowMaximumSizeToDivergeFromCoreSize thread-pool property +- [Pull 1585](https://github.com/Netflix/Hystrix/pull/1585) Fix actualMaximumSize Codahale threadpool metric calculation +- [Pull 1567](https://github.com/Netflix/Hystrix/pull/1567) Fix interaction between ExceptionNotWrappedInHystrix and HystrixBadRequestException. Thanks @gagoman ! +- [Pull 1576](https://github.com/Netflix/Hystrix/pull/1576) Fix permyriad calculation for 99.9p latency +- [Pull 1524](https://github.com/Netflix/Hystrix/pull/1524) Javanica: Support rx.Single or rx.Completable types. Thanks @dmgcodevil ! +- [Pull 1574](https://github.com/Netflix/Hystrix/pull/1574) Add unit-test for using a Completable in a HystrixObservableCommand +- [Pull 1572](https://github.com/Netflix/Hystrix/pull/1572) Javanica: Wire up maximumSize thread-pool property. Thanks @dmgcodevil ! +- [Pull 1573](https://github.com/Netflix/Hystrix/pull/1573) Javanica: Don't get cause from HystrixBadRequestException if null. Thanks @dmgcodevil ! +- [Pull 1570](https://github.com/Netflix/Hystrix/pull/1570) Only create HystrixContextRunnable in timeout case lazily, when timeout is fired +- [Pull 1568](https://github.com/Netflix/Hystrix/pull/1568) Made circuit-opening happen in background thread, powered by metric streams +- [Pull 1561](https://github.com/Netflix/Hystrix/pull/1561) Add error-handling for unexpected errors to servlet writes in metric sample servlet +- [Pull 1556](https://github.com/Netflix/Hystrix/pull/1556) Typo fix in Javanica fallback log. Thanks @Thunderforge ! +- [Pull 1551](https://github.com/Netflix/Hystrix/pull/1551) Match colors in multiple circuit-breaker status case. Thanks @eunmin ! +- [Pull 1547](https://github.com/Netflix/Hystrix/pull/1547) Support multiple circuit-breaker statuses in dashboard against aggregated data. Thanks @eunmin ! +- [Pull 1539](https://github.com/Netflix/Hystrix/pull/1539) Fix unintentionally shared variable in hystrix-metrics-event-stream-jaxrs. Thanks @justinjose28 ! +- [Pull 1535](https://github.com/Netflix/Hystrix/pull/1535) Move markCommandExecution after markEvent SUCCESS to allow eventNotifier to have full context of execution. Thanks @bltb! + +### Version 1.5.11 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.11%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.11/)) ### + +- [Pull 1531](https://github.com/Netflix/Hystrix/pull/1531) Add assertion to dashboard receiving metrics data. Thanks @lholmquist ! +- [Pull 1529](https://github.com/Netflix/Hystrix/pull/1529) Remove commons-collection as dependency of hystrix-javanica. Thanks @Psynbiotik ! +- [Pull 1532](https://github.com/Netflix/Hystrix/pull/1532) Move metrics subscription out of synchronized block in HealthCountsStream to limit time spent holding a lock. +- [Pull 1513](https://github.com/Netflix/Hystrix/pull/1513) Fixed COMMAND_MAX_ACTIVE metrics for Coda Hale metrics. Thanks @chrisgray ! +- [Pull 1515](https://github.com/Netflix/Hystrix/pull/1515) Fixed comment typo in HystrixCommand. Thanks @kmkr ! +- [Pull 1512](https://github.com/Netflix/Hystrix/pull/1512) Upgrade codahale metrics-core to 3.2.2. Thanks @chrisgray ! +- [Pull 1507](https://github.com/Netflix/Hystrix/pull/1507) README typo fix. Thanks @PiperChester ! +- [Pull 1503](https://github.com/Netflix/Hystrix/pull/1503) Allow BadRequest exceptions to not be attached if user explicitly wants unwrapped exceptions. Thanks @mNantern ! +- [Pull 1502](https://github.com/Netflix/Hystrix/pull/1502) Remove useless code in HystrixConcurrencyStrategy. Thanks @zzzvvvxxxd ! +- [Pull 1495](https://github.com/Netflix/Hystrix/pull/1495) Add hystrix-metrics-event-stream-jaxrs. Thanks @justinjose28 ! +- [Pull 1498](https://github.com/Netflix/Hystrix/pull/1498) Upgrade Servo to 0.10.1 in hystrix-servo-metrics-publisher + +### Version 1.5.10 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.10%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.10/)) ### + +- [Pull 1489](https://github.com/Netflix/Hystrix/pull/1489) Added rollingMaxConcurrentExecutionCount to CodaHale metrics publisher. Thanks @LuboVarga ! +- [Pull 1481](https://github.com/Netflix/Hystrix/pull/1481) Add sanity checking to HystrixCommandAspect to debug unreproducible cases. Thanks @dmgcodevil ! +- [Pull 1482](https://github.com/Netflix/Hystrix/pull/1482) Make it possible to re-use fallback methods in Javanica. (Addresses #1446). Thanks @dmgcodevil ! +- [Pull 1488](https://github.com/Netflix/Hystrix/pull/1488) Fix spelling mistakes in Javanica docs. Thanks @bltb! +- [Pull 1475](https://github.com/Netflix/Hystrix/pull/1475) Added example usage of CodaHale metrics publisher. Thanks @LuboVarga ! +- [Pull 1469](https://github.com/Netflix/Hystrix/pull/1469) Fix possible concurrency bug. Thanks @petercla! +- [Pull 1453](https://github.com/Netflix/Hystrix/pull/1453) Add Javanica unit test for NotWrapped checked exception. Thanks @tbvh! + +### Version 1.5.9 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.9%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.9/)) ### + +* [Pull 1423](https://github.com/Netflix/Hystrix/pull/1423) Write correct value to error log when maximumSize < coreSize. Thanks @diver-in-sky! +* [Pull 1412](https://github.com/Netflix/Hystrix/pull/1412) Javanica: raiseHystrixExceptions support for Observables. Thanks @michaelcowan ! +* [Pull 1441](https://github.com/Netflix/Hystrix/pull/1441) Use Gretty Gradle plugin for hystrix-examples-webapp +* [Pull 1442](https://github.com/Netflix/Hystrix/pull/1442) Fix handling of client-connect/disconnect never getting released if it occurs before metrics start getting produced by hystrix-metrics-event-stream. Thanks for review, @mattnelson! +* [Pull 1444](https://github.com/Netflix/Hystrix/pull/1444) More efficient server thread release in hystrix-metrics-event-stream. Thanks @mattnelson for the suggestion! +* [Pull 1443](https://github.com/Netflix/Hystrix/pull/1443) Use Gretty Gradle plugin for hystrix-dashboard +* [Pull 1445](https://github.com/Netflix/Hystrix/pull/1445) Add missing onUnsubscribe hook to execution hooks +* [Pull 1414](https://github.com/Netflix/Hystrix/pull/1414) Introduce NotWrappedByHystrix exception type to indicate Hystrix should propagate it back without wrapping in a HystrixRuntimeException. Thanks @tbvh! +* [Pull 1448](https://github.com/Netflix/Hystrix/pull/1448) Remove dependency on jackson-cbor in hystrix-serialization. This belongs in a different module. Existing public methods now throw an exception. +* [Pull 1435](https://github.com/Netflix/Hystrix/pull/1435) Allow the property `allowMaximumSixeToDivergeFromCoreSize` to be set dynamically. Thanks @ptab! +* [Pull 1447](https://github.com/Netflix/Hystrix/pull/1447) Allow the property `allowMaximumSixeToDivergeFromCoreSize` to be set dynamically. +* [Pull 1449](https://github.com/Netflix/Hystrix/pull/1435) Introduce a distinction between `maximumSize` (configured value) and `actualMaximumSize` (value used to set the thread pool maximum size). Publish both values to make understanding configuration more straightforward. Thanks @ptab! + +### Version 1.5.8 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.8%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.8/)) ### + +* [Pull 1419](https://github.com/Netflix/Hystrix/pull/1419) When user has not opted in to letting core/maximum threadpools diverge, ensure dynamic updates to coreSize apply to both +* [Pull 1415](https://github.com/Netflix/Hystrix/pull/1415) Fix spelling mistake in comments. Thanks @starlight36 ! + +### Version 1.5.7 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.7%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.7/)) ### + +* [Pull 1408](https://github.com/Netflix/Hystrix/pull/1408) Fix Clojure key name for collapsing. Thanks @crimeminister ! +* [Pull 1407](https://github.com/Netflix/Hystrix/pull/1407) Reset percentile snapshot whenever all HystrixRollingPercentile buckets are empty +* [Pull 1397](https://github.com/Netflix/Hystrix/pull/1397) Javanica: Add option to raise HystrixRuntimeException +* [Pull 1399](https://github.com/Netflix/Hystrix/pull/1399) Add configuration to make users opt-in to allowing coreSize and maximumSize to diverge. See config [here] (https://github.com/Netflix/Hystrix/wiki/Configuration#allowMaximumSizeToDivergeFromCoreSize) +* [Pull 1396](https://github.com/Netflix/Hystrix/pull/1396) If command is unsubscribed before any work is done, return Observable.empty(). +* [Pull 1393](https://github.com/Netflix/Hystrix/pull/1393) Javanica: Performance improvement by caching weavingMode boolean. Thanks @ricardoletgo ! +* [Pull 1389](https://github.com/Netflix/Hystrix/pull/1389) Javanica: Send fallback exception to client instead of primary command. Thanks @dmgcodevil ! +* [Pull 1385](https://github.com/Netflix/Hystrix/pull/1385) Bump jmh Gradle plugin to 0.3.1. Thanks @monkey-mas! +* [Pull 1382](https://github.com/Netflix/Hystrix/pull/1382) Bump jmh to 1.15. Thanks @monkey-mas! +* [Pull 1380](https://github.com/Netflix/Hystrix/pull/1380) Add jmh test for open-circuit case +* [Pull 1376](https://github.com/Netflix/Hystrix/pull/1376) Clean up documentation around thread keep-alive. Thanks @bitb ! +* [Pull 1375](https://github.com/Netflix/Hystrix/pull/1375) Remove cancelled tasks from threadpool queue +* [Pull 1371](https://github.com/Netflix/Hystrix/pull/1371) Allow core and maximum size of threadpools to diverge. + +### Version 1.5.6 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.6%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.6/)) ### + +* [Pull 1368](https://github.com/Netflix/Hystrix/pull/1368) Upgrade jmh to 1.14.1 +* [Pull 1365](https://github.com/Netflix/Hystrix/pull/1365) Upgrade to Gradle 3.1 / Nebula 3.4.0 +* [Pull 1364](https://github.com/Netflix/Hystrix/pull/1364) Fix backwards-incompatibility introduced in #1356 +* [Pull 1363](https://github.com/Netflix/Hystrix/pull/1363) Fix metrics regression where thread pool objects without any executions were being sent out in metrics streams +* [Pull 1360](https://github.com/Netflix/Hystrix/pull/1360) Convert command-construction jmh test to single-shot +* [Pull 1356](https://github.com/Netflix/Hystrix/pull/1356) Add better AppEngine detection mechanism that allows GAE-Flexible to work like any other JVM. Thanks @cadef! +* [Pull 1353](https://github.com/Netflix/Hystrix/pull/1353) Upgrade to RxJava 1.2.0 +* [Pull 1351](https://github.com/Netflix/Hystrix/pull/1351) Remove histogram object-pooling +* [Pull 1336](https://github.com/Netflix/Hystrix/pull/1336) Overall Dashboard UX improvements. Thanks @kennedyoliveira ! +* [Pull 1320](https://github.com/Netflix/Hystrix/pull/1320) Adding example of HystrixObservableCollapser. Thanks @zsoltm ! +* [Pull 1341](https://github.com/Netflix/Hystrix/pull/1341) Javanica fix for handling commands with generic types. Thanks @dmgcodevil ! +* [Pull 1340](https://github.com/Netflix/Hystrix/pull/1340) Refactor how commands determine if fallbacks are user-defined + +### Version 1.5.5 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.5%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.5/)) ### + +* [Pull 1323](https://github.com/Netflix/Hystrix/pull/1323) Remove ReactiveSocket modules and change Jenkins release process back to JDK7 + +### Version 1.5.4 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.4%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.4/)) ### + +* [Pull 1308](https://github.com/Netflix/Hystrix/pull/1308) Update jmh to 1.13 +* [Pull 1310](https://github.com/Netflix/Hystrix/pull/1310) Update nebula.netflixoss Gradle plugin to 3.3.0 +* [Pull 1309](https://github.com/Netflix/Hystrix/pull/1309) Update HdrHistogram to 2.1.9 +* [Pull 1307](https://github.com/Netflix/Hystrix/pull/1307) Update RxJava to 1.1.8 +* [Pull 1306](https://github.com/Netflix/Hystrix/pull/1306) Update Clojure to 1.7.0 and nebula-clojure-plugin to 4.0.1 +* [Pull 1305](https://github.com/Netflix/Hystrix/pull/1305) Update Gradle to 2.14 +* [Pull 1304](https://github.com/Netflix/Hystrix/pull/1304) Make all metrics streams multicast. +* [Pull 1303](https://github.com/Netflix/Hystrix/pull/1303) Update RxNetty to 0.4.17 +* [Pull 1302](https://github.com/Netflix/Hystrix/pull/1302) Manual merge of #1265. Interrupt execution thread on HystrixCommand#queue()#cancel(true). Thanks @mmanciop! +* [Pull 1300](https://github.com/Netflix/Hystrix/pull/1300) Update all completion state for scalar command in the onNext handling +* [Pull 1294](https://github.com/Netflix/Hystrix/pull/1294) Make sure that threadpools shutdown when asked to. Thanks @thesmith! +* [Pull 1297](https://github.com/Netflix/Hystrix/pull/1297) Fix typo in README. Thanks @ManishMaheshwari! +* [Pull 1295](https://github.com/Netflix/Hystrix/pull/1295) Fix typo in README. Thanks @C-Otto! +* [Pull 1273](https://github.com/Netflix/Hystrix/pull/1273) Corrected ignoreExceptions for Observable-returning methods. Thanks @jbojar! +* [Pull 1197](https://github.com/Netflix/Hystrix/pull/1197) Eureka integration for Hystrix dashboard. Thanks @diegopacheco! +* [Pull 1278](https://github.com/Netflix/Hystrix/pull/1278) Prevent duplicate arguments from getting into a single collapser RequestBatch +* [Pull 1277](https://github.com/Netflix/Hystrix/pull/1277) Make HystrixCollapser.toObservable lazy +* [Pull 1276](https://github.com/Netflix/Hystrix/pull/1276) Make HystrixObservableCollapser.toObservable lazy +* [Pull 1271](https://github.com/Netflix/Hystrix/pull/1271) Fix race condition in all of the Hystrix*Key.asKey methods. Thanks @daniilguit! +* [Pull 1274](https://github.com/Netflix/Hystrix/pull/1274) Make AbstractCommand.toObservable lazy +* [Pull 1270](https://github.com/Netflix/Hystrix/pull/1270) Fix deprecation warnings by upgrading RxJava and Netty usages +* [Pull 1269](https://github.com/Netflix/Hystrix/pull/1269) Rework hystrix-data-stream module to just include serialization logic +* [Pull 1259](https://github.com/Netflix/Hystrix/pull/1259) Javanica: added DefaultProperties annotation. Thanks @dmgcodevil! +* [Pull 1261](https://github.com/Netflix/Hystrix/pull/1261) Add toString() to key implementations. Thanks @mebigfatguy! +* [Pull 1258](https://github.com/Netflix/Hystrix/pull/1258) Javanica: Change getMethod to recursively search in parent types. Thanks @dmgcodevil! +* [Pull 1255](https://github.com/Netflix/Hystrix/pull/1255) Introduce intermediate data streams module [LATER REVERTED - SEE #1269 ABOVE] +* [Pull 1254](https://github.com/Netflix/Hystrix/pull/1254) Allow multiple consumers of sample data to only trigger work once and share data +* [Pull 1251](https://github.com/Netflix/Hystrix/pull/1251) Fix Dashboard RPS +* [Pull 1247](https://github.com/Netflix/Hystrix/pull/1247) Call thread pool size setters only when pool size changes. Thanks @yanglifan! +* [Pull 1246](https://github.com/Netflix/Hystrix/pull/1246) Move HystrixDashboardStream to hystrix-core +* [Pull 1244](https://github.com/Netflix/Hystrix/pull/1244) Fix handling of invalid weavingMode property. Thanks @mebigfatguy! +* [Pull 1238](https://github.com/Netflix/Hystrix/pull/1238) Local variable caching fixups. Thanks @mebigfatguy! +* [Pull 1236](https://github.com/Netflix/Hystrix/pull/1236) ReactiveSocket metrics client/server +* [Pull 1235](https://github.com/Netflix/Hystrix/pull/1235) Add demo that composes async command executions +* [Pull 1231](https://github.com/Netflix/Hystrix/pull/1231) Make inner classes static where possible, and remove outer class reference. Thanks @mebigfatguy! +* [Pull 1229](https://github.com/Netflix/Hystrix/pull/1229) Remove dead Setter class from RequestCollapserFactory. Thanks @mebigfatguy! +* [Pull 1225](https://github.com/Netflix/Hystrix/pull/1225) Remove unused thunk from HystrixCommandMetrics. Thanks @mebigfatguy! +* [Pull 1224](https://github.com/Netflix/Hystrix/pull/1224) Remove dead field from RequestCollapserFactory. Thanks @mebigfatguy! +* [Pull 1221](https://github.com/Netflix/Hystrix/pull/1221) Improve grammar in comments and log messages. Thanks @dysmento ! +* [Pull 1211](https://github.com/Netflix/Hystrix/pull/1211) Add ReactiveSocket metrics stream. Thanks @robertroeser! +* [Pull 1219](https://github.com/Netflix/Hystrix/pull/1219) Move map lookup outside of loop in collapser code. Thanks @mebigfatguy! + +### Version 1.5.3 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.3%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.3/)) ### + +The largest new feature in this release is that cancellation is now supported. Either calling `cancel()` on the result of `HystrixCommand.queue()` or `unsubscribe()` on the result of `HystrixCommand.toObservable().subscribe()` will propagate the cancellation to the underlying work. + +* [Pull 1218](https://github.com/Netflix/Hystrix/pull/1218) Fixing unsubscription races by modeling explicit FSMs for command and thread execution state +* [Pull 1217](https://github.com/Netflix/Hystrix/pull/1217) Remove dead code in RequestEventsStream. Thanks @mebigfatguy! +* [Pull 1214](https://github.com/Netflix/Hystrix/pull/1214) Fix Servo metric calculation. Bug identified by @AchimWe. +* [Pull 1213](https://github.com/Netflix/Hystrix/pull/1213) Use parameterized logging. Thanks @mebigfatguy! +* [Pull 1212](https://github.com/Netflix/Hystrix/pull/1212) Correct logging contexts. Thanks @mebigfatguy! +* [Pull 1210](https://github.com/Netflix/Hystrix/pull/1210) Javanica: code optimization to fetch parameters only if needed. Thanks @mebigfatguy! +* [Pull 1209](https://github.com/Netflix/Hystrix/pull/1209) Fixed thread-state cleanup to happen on unsubscribe or terminate +* [Pull 1208](https://github.com/Netflix/Hystrix/pull/1208) Reorganization of HystrixCollapser/HystrixObservableCollapser logic to support cancellation +* [Pull 1207](https://github.com/Netflix/Hystrix/pull/1207) Fix command concurrency metric in light of cancellation +* [Pull 1206](https://github.com/Netflix/Hystrix/pull/1206) Added subscribeOn to HystrixObservableCommand in JMH test to make it async +* [Pull 1204](https://github.com/Netflix/Hystrix/pull/1204) Reorganization of AbstractCommand logic to support cancellation +* [Pull 1198](https://github.com/Netflix/Hystrix/pull/1198) Release semaphores upon cancellation. +* [Pull 1194](https://github.com/Netflix/Hystrix/pull/1194) Improved tests readability a bit by letting exceptions propagate out. Thanks @caarlos0! +* [Pull 1193](https://github.com/Netflix/Hystrix/pull/1193) More tests using HystrixRequestContext rule. Thanks @caarlos0! +* [Pull 1181](https://github.com/Netflix/Hystrix/pull/1181) Deprecate getter and setter for unused collapsingEnabled property in Collapser Setter. Thanks @nluchs! +* [Pull 1184](https://github.com/Netflix/Hystrix/pull/1184) migrating all hystrix-javanica tests to hystrix-junit. Thanks @caarlos0! +* [Pull 1147](https://github.com/Netflix/Hystrix/pull/1147) Added hystrix-junit. Thanks @caarlos0! + +### Version 1.4.26 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.26%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.26/)) ### + +* [Pull 1169](https://github.com/Netflix/Hystrix/pull/1169) Javanica: Switch hystrix-javanica to use getExecutionException, which returns Exception object even when command is not executed + +### Version 1.5.2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.2%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.2/)) ### + +* [Pull 1171](https://github.com/Netflix/Hystrix/pull/1171) Do all histogram latency summarization upfront to minimize storage/operations on them +* [Pull 1167](https://github.com/Netflix/Hystrix/pull/1167) Javanica: Switch hystrix-javanica to use getExecutionException, which returns Exception object even when command is not executed +* [Pull 1157](https://github.com/Netflix/Hystrix/pull/1157) Make HystrixMetricsPoller a daemon thread +* [Pull 1154](https://github.com/Netflix/Hystrix/pull/1154) Remove more unused methods +* [Pull 1151](https://github.com/Netflix/Hystrix/pull/1151) Remove unused method in HystrixCollapserProperties +* [Pull 1149](https://github.com/Netflix/Hystrix/pull/1149) Make queue size of MetricJsonListener configurable +* [Pull 1124](https://github.com/Netflix/Hystrix/pull/1124) Turning down loglevels of metrics streams +* [Pull 1120](https://github.com/Netflix/Hystrix/pull/1120) Making the HystrixTimeoutException instance per-command, not static + +### Version 1.4.25 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.25%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.25/)) ### + +* [Pull 1114](https://github.com/Netflix/Hystrix/pull/1114) Make queue size of MetricsJsonListener configurable +* [Pull 1121](https://github.com/Netflix/Hystrix/pull/1121) HystrixTimeoutException is non-static for better stacktrace + +Artifacts: [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.25%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.25/) + +### Version 1.5.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.1%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.1/)) ### + +* [Pull 1118](https://github.com/Netflix/Hystrix/pull/1118) Revert #1075. Return userThreadLatency to metrics, mostly to maintain format compatibility with data streams from 1.4.x +* [Pull 1116](https://github.com/Netflix/Hystrix/pull/1116) Fix references to underscore.js over HTTPS +* [Pull 1115](https://github.com/Netflix/Hystrix/pull/1115) Fix LICENSE reference in README +* [Pull 1111](https://github.com/Netflix/Hystrix/pull/1111) HystrixRequestContext implements Closeable + +### Version 1.5.0 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.0%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.0/)) ### + +The general premise of this release is to make metrics more flexible within Hystrix. See https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring for a deep dive on the new metrics architecture. The high-level approach is to model metrics directly as a stream, so that Hystrix metrics consumers may aggregate the metrics as they wish. In 1.4.x and prior releases, `HystrixRollingNumber` and `HystrixRollingPercentile` were used to store aggregate command counters and command latencies, respectively. These are no longer used. + +Instead, new concepts like `HystrixCommandCompletionStream` are present. These may be consumed by a rolling, summarizing data structure (like `HystrixRollingNumber`), or they may be consumed without any aggregation at all. This should allow for all metrics processing to move off-box, if you desire to add that piece to your infrastructure. + +This version should be backwards-compatible with v1.4.x. If you find otherwise, please submit a Hystrix issue as it was unintentional. + +This version also introduces new metric streams: ([configuration](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#configuration-stream) and [Utilization](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#utilization-stream)) have been added, along with a [request-scoped stream](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#request-streams). + +Archaius is now a soft-dependency of Hystrix, so you can supply your own configuration mechanism. + +Some known semantic changes: +* Latencies for timeouts and bad-requests are now included in command latency +* Latency distribution percentiles are now calculated with HdrHistogram library and don't have a max number of elements in the distribution +* Previously, HealthCounts data allowed reads to see the value in the "hot" bucket. (the one currently being written to). That does not happen anymore - only full read-only buckets are available for reads. +* Bucket rolling now happens via Rx background threads instead of unlucky Hystrix command threads. This makes command performance more predictable. User-thread latency is now practically indistinguishable from command latency. + +### Version 1.4.24 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.24%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.24/)) ### + +* [Pull 1113](https://github.com/Netflix/Hystrix/pull/1113) Make HystrixRequestContext implement Closeable +* [Pull 1112](https://github.com/Netflix/Hystrix/pull/1112) Upgrade to latest Nebula Gradle plugin +* [Pull 1110](https://github.com/Netflix/Hystrix/pull/1110) Upgrade to RxJava 1.1.1 +* [Pull 1108](https://github.com/Netflix/Hystrix/pull/1108) Javanica HystrixRequestCacheManager should use the concurrency strategy + +### Version 1.5.0-rc.5 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.0-rc.5%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.0-rc.5/)) ### + +This version does not have any known bugs, but is not recommended for production use until 1.5.0. + +Included changes: + +* [Pull 1102](https://github.com/Netflix/Hystrix/pull/1102) Bugfix to null check on HystrixRequestCache context + +### Version 1.5.0-rc.4 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.0-rc.4%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.0-rc.4/)) ### + +This version does not have any known bugs, but is not recommended for production use until 1.5.0. + +Included changes: + +* [Pull 1099](https://github.com/Netflix/Hystrix/pull/1099) Bugfix to get Hystrix dashboard operational again + +### Version 1.5.0-rc.3 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.0-rc.3%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.0-rc.3/)) ### + +This version does not have any known bugs, but is not recommended for production use until 1.5.0. + +A few dependency bumps, but the major change here is that Archaius is now a soft dependency of hystrix-core. Thanks to @agentgt for the PR!. Thanks also to @caarlos0 for the NPE fix in HystrixRequestCache. + +Included changes: + +* [Pull 1079](https://github.com/Netflix/Hystrix/pull/1079) Remove dynamic config lookup in HystrixThreadPool +* [Pull 1081](https://github.com/Netflix/Hystrix/pull/1081) Cleanup hystrix-javanica BadRequest docs +* [Pull 1093](https://github.com/Netflix/Hystrix/pull/1093) Fix NPE in HystrixRequestCache when HystrixRequestContext not initialized +* [Pull 1083](https://github.com/Netflix/Hystrix/pull/1083) Made Archaius a soft dependency of hystrix-core. It is now possible to run without Archaius and rely on j.u.l.ServiceLoader or system properties only +* [Pull 1095](https://github.com/Netflix/Hystrix/pull/1095) Upgrade to Nebula netflixoss 3.2.3 +* [Pull 1096](https://github.com/Netflix/Hystrix/pull/1096) Upgrade to RxJava 1.1.1 +* [Pull 1097](https://github.com/Netflix/Hystrix/pull/1097) Fix POM generation by excluding WAR artifacts + +### Version 1.5.0-rc.2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.0-rc.2%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.0-rc.2/)) ### + +This version does not have any known bugs, but is not recommended for production use until 1.5.0. + +This is mostly a new set of features building on top of Release Candidate 1. Specifically, some sample streams ([Configuration](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#configuration-stream) and [Utilization](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#utilization-stream)) have been added, along with a [request-scoped stream](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#request-streams). + +Included changes: + +* [Pull 1050](https://github.com/Netflix/Hystrix/pull/1050) Modular command construction +* [Pull 1061](https://github.com/Netflix/Hystrix/pull/1061) Sample config/utilization streams, and request-scoped streams +* [Pull 1064](https://github.com/Netflix/Hystrix/pull/1064) Safer enum references in case mismatched Hystrix jars are deployed together +* [Pull 1066](https://github.com/Netflix/Hystrix/pull/1066) Layer of abstraction on top of ThreadFactory, so AppEngine can run Hystrix +* [Pull 1067](https://github.com/Netflix/Hystrix/pull/1067) Decouple sample stream JSON from servlets +* [Pull 1067](https://github.com/Netflix/Hystrix/pull/1068) Decouple request-scoped stream JSON from servlets +* [Pull 1075](https://github.com/Netflix/Hystrix/pull/1075) Deprecate userThreadLatency, since it is practically identical to executionLatency now + +### Version 1.5.0-rc.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.0-rc.1%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.0-rc.1/)) ### + +This version does not have any known bugs, but *is not* recommended for production use until 1.5.0. + +The general premise of this release is to make metrics more flexible within Hystrix. See https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring for a deep dive on the new metrics architecture. The high-level view is to make the metrics primitive a stream instead of an aggregate. In 1.4.x and prior releases, `HystrixRollingNumber` and `HystrixRollingPercentile` were used to store aggregate command counters and command latencies, respectively. These are no longer used. + +Instead, new concepts like `HystrixCommandCompletionStream` are present. These may be consumed by a rolling, summarizing data structure (like `HystrixRollingNumber`), or they may be consumed without any aggregation at all. This should allow for all metrics processing to move off-box, if you desire to add that piece to your infrastructure. + +This version should be backwards-compatible with v1.4.x. If you find otherwise, please submit a Hystrix issue as it was unintentional. + +Some known semantic changes: +* Latencies for timeouts and bad-requests are now included in command latency +* Latency distribution percentiles are now calculated with HdrHistogram library and don't have a max number of elements in the distribution +* Previously, HealthCounts data allowed reads to see the value in the "hot" bucket. (the one currently being written to). That does not happen anymore - only full read-only buckets are available for reads. +* Bucket rolling now happens via Rx background threads instead of unlucky Hystrix command threads. This makes command performance more predictable. User-thread latency is now practically indistinguishable from command latency. + +Artifacts: [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.5.0-rc.1%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.5.0-rc.1/) + +### Version 1.4.23 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.23%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.23/)) ### + +* [Pull 1032](https://github.com/Netflix/Hystrix/pull/1032) Make number of timer threads a piece of config (with Archaius integration) +* [Pull 1045](https://github.com/Netflix/Hystrix/pull/1045) Documentation cleanup in HystrixCommandProperties +* [Pull 1044](https://github.com/Netflix/Hystrix/pull/1044) Add request context and HystrixObservableCommand to command execution JMH tests +* [Pull 1043](https://github.com/Netflix/Hystrix/pull/1043) HystrixObservableCollapser emits error to each submitter when batch command encounters error +* [Pull 1039](https://github.com/Netflix/Hystrix/pull/1039) Use thread-safe data structure for storing list of command keys per-thread +* [Pull 1036](https://github.com/Netflix/Hystrix/pull/1036) Remove redundant ConcurrentHashMap read when getting name from command class +* [Pull 1035](https://github.com/Netflix/Hystrix/pull/1035) Rename command execution JMH tests +* [Pull 1034](https://github.com/Netflix/Hystrix/pull/1034) Remove SHORT_CIRCUITED events from health counts calculation +* [Pull 1027](https://github.com/Netflix/Hystrix/pull/1027) Fix typo in hystrix-examples-webapp documentation + +### Version 1.4.22 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.22%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.22/)) ### + +* [Pull 1019](https://github.com/Netflix/Hystrix/pull/1019) hystrix-dashboard: Swap magnifying glass logos +* [Commit 41e9c210f044fe822625acc6e23c5167e56c3f09](https://github.com/Netflix/Hystrix/commit/41e9c210f044fe822625acc6e23c5167e56c3f09) Add OSSMETADATA +* [Pull 1014](https://github.com/Netflix/Hystrix/pull/1014) Upgrade to RxJava 1.1.0 +* [Pull 1009](https://github.com/Netflix/Hystrix/pull/1009) hystrix-javanica: Upgrade to AspectJ 1.8.6 +* [Pull 1008](https://github.com/Netflix/Hystrix/pull/1008) Add AbstractCommand.getExecutionException +* [Pull 1006](https://github.com/Netflix/Hystrix/pull/1006) Add Cobertura plugin +* [Pull 1005](https://github.com/Netflix/Hystrix/pull/1005) Upgrade RxJava to 1.0.17 +* [Pull 1000](https://github.com/Netflix/Hystrix/pull/1000) Fix network-auditor Javadoc +* [Pull 999](https://github.com/Netflix/Hystrix/pull/999) Upgrade Javassist within hystrix-network-auditor-agent +* [Pull 992](https://github.com/Netflix/Hystrix/pull/992) hystrix-dashboard: Remove validation error message when adding a stream +* [Pull 977](https://github.com/Netflix/Hystrix/pull/977) hystrix-javanica support for Observable command + +### Version 1.4.21 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.21%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.21/)) ### + +* [Pull 978](https://github.com/Netflix/Hystrix/pull/978) Upgrade commons-collections to 3.2.2 +* [Pull 959](https://github.com/Netflix/Hystrix/pull/959) Support multiple metric streams in hystrix-dashboard +* [Pull 976](https://github.com/Netflix/Hystrix/pull/976) Prevent execution observable from running when hook throws an error in onXXXStart +* [Pull 972](https://github.com/Netflix/Hystrix/pull/972) Mark servlet-api dependency as 'provided' +* [Pull 968](https://github.com/Netflix/Hystrix/pull/968) Add defaultSetter() to properties classes to workaround GROOVY-6286 + +### Version 1.4.20 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.20%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.20/)) ### + +* [Pull 965](https://github.com/Netflix/Hystrix/pull/965) Upgrade Nebula Gradle plugin +* [Pull 962](https://github.com/Netflix/Hystrix/pull/962) Javanica: Support for async commands +* [Pull 960](https://github.com/Netflix/Hystrix/pull/960) Avoid Clojure reflection in hystrix-clj +* [Pull 957](https://github.com/Netflix/Hystrix/pull/957) Javanica: Fix threadpool properties +* [Pull 956](https://github.com/Netflix/Hystrix/pull/956) Upgrade JMH from 1.10.3 to 1.11.1 +* [Pull 945](https://github.com/Netflix/Hystrix/pull/945) Javanica: Compile-time weaving support +* [Pull 952](https://github.com/Netflix/Hystrix/pull/952) Tolerate lack of RequestContext better for custom concurrency strategies +* [Pull 947](https://github.com/Netflix/Hystrix/pull/947) Upgrade RxNetty to 0.4.12 for RxNetty metrics stream +* [Pull 946](https://github.com/Netflix/Hystrix/pull/946) More extension-friendly Yammer metrics publisher +* [Pull 944](https://github.com/Netflix/Hystrix/pull/944) Fix generated POM to include dependencies in 'compile' scope +* [Pull 942](https://github.com/Netflix/Hystrix/pull/942) Fix metrics stream fallbackEmit metric +* [Pull 941](https://github.com/Netflix/Hystrix/pull/941) Add FALLBACK_MISSING event type and metric + +### Version 1.4.19 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.19%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.19/)) ### + +This version should be the exact same as 1.4.20, but suffered problems during the publishing process. Please use 1.4.20 instead. + +### Version 1.4.18 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.18%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.18/)) ### + +* [Pull 934](https://github.com/Netflix/Hystrix/pull/934) Remove duplicate EventSource from dashboard +* [Pull 931](https://github.com/Netflix/Hystrix/pull/931) Make HystrixTimeoutException public +* [Pull 930](https://github.com/Netflix/Hystrix/pull/930) Support collapser metrics in HystrixMetricPublisher implementations +* [Pull 927](https://github.com/Netflix/Hystrix/pull/927) Dashboard fix to isCircuitBreakerOpen + +### Version 1.4.17 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.17%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.17/)) ### + +* [Pull 924](https://github.com/Netflix/Hystrix/pull/924) Dashboard protection against XSS +* [Pull 923](https://github.com/Netflix/Hystrix/pull/923) Upgrade to RxJava 1.0.14 +* [Pull 922](https://github.com/Netflix/Hystrix/pull/922) Add DEBUG tag to Servo rolling counter and made it a GaugeMetric + +### Version 1.4.16 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.16%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.16/)) ### + +* [Pull 917](https://github.com/Netflix/Hystrix/pull/917) Better version of making servo-metrics-publisher extension-friendly +* [Pull 912](https://github.com/Netflix/Hystrix/pull/912) Only look up if HystrixRequestCache is enabled once per HystrixObservableCollapser-invocation +* [Pull 911](https://github.com/Netflix/Hystrix/pull/911) Unit test for large threadpool/small queue case in command execution +* [Pull 910](https://github.com/Netflix/Hystrix/pull/910) Make servo-metrics-publisher more extension-friendly +* [Pull 905](https://github.com/Netflix/Hystrix/pull/905) HystrixObservableCollapser examples +* [Pull 902](https://github.com/Netflix/Hystrix/pull/902) Cleanup HystrixObservableCollapser unit tests +* [Pull 900](https://github.com/Netflix/Hystrix/pull/900) Remove commons-collections dependency from hystrix-javanica +* [Pull 897](https://github.com/Netflix/Hystrix/pull/897) Fix missing null check in hystrix-javanica HystrixCacheKeyGenerator + +### Version 1.4.15 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.15%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.15/)) ### + +* [Pull 890](https://github.com/Netflix/Hystrix/pull/890) Allow multiple responses per collapser argument. No semantic change for HystrixCollapser, but a bugfix to HystrixObservableCollapser +* [Pull 892](https://github.com/Netflix/Hystrix/pull/892) Cache Setter in MultithreadedMetricsPerfTest +* [Pull 891](https://github.com/Netflix/Hystrix/pull/891) Add request context to command JMH tests +* [Pull 889](https://github.com/Netflix/Hystrix/pull/889) Replace subscribe() in RequestBatch with unsafeUnsubscribe() +* [Pull 887](https://github.com/Netflix/Hystrix/pull/887) Only look up if HystrixRequestCache is enabled once per collapser-invocation +* [Pull 885](https://github.com/Netflix/Hystrix/pull/885) Only look up if HystrixRequestCache is enabled once per command-invocation +* [Pull 876](https://github.com/Netflix/Hystrix/pull/876) Report BAD_REQUEST to HystrixRequestLog +* [Pull 861](https://github.com/Netflix/Hystrix/pull/861) Make hystrix-javanica OSGI-compliant +* [Pull 856](https://github.com/Netflix/Hystrix/pull/856) Add missing licenses +* [Pull 855](https://github.com/Netflix/Hystrix/pull/855) Save allocation if using a convenience constructor for HystrixCommand +* [Pull 853](https://github.com/Netflix/Hystrix/pull/853) Run Travis build in a container +* [Pull 848](https://github.com/Netflix/Hystrix/pull/848) Unit tests to demonstrate HystrixRequestLog was not experiencing data races + +### Version 1.4.14 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.14%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.14/)) ### + +* [Pull 852](https://github.com/Netflix/Hystrix/pull/852) Fix hystrix-clj that was blocking http://dev.clojure.org/jira/browse/CLJ-1232 +* [Pull 849](https://github.com/Netflix/Hystrix/pull/849) Unit tests for HystrixCommands that are part of a class hierarchy with other HystrixCommands + +### Version 1.4.13 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.13%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.13/)) ### + +* [Pull 839](https://github.com/Netflix/Hystrix/pull/839) Fix typo in hystrix-dashboard js +* [Pull 838](https://github.com/Netflix/Hystrix/pull/838) Add back unit tests for Hystrix class and its reset() method in particular +* [Pull 837](https://github.com/Netflix/Hystrix/pull/837) Upgrade to RxJava 1.0.13 +* [Pull 830](https://github.com/Netflix/Hystrix/pull/830) Add validation for rollingCountBadRequest in hystrix-dashboard + +### Version 1.4.12 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.12%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.12/)) ### + +* [Pull 826](https://github.com/Netflix/Hystrix/pull/826) Safely handle negative input for delay parameter to metrics stream servlet +* [Pull 825](https://github.com/Netflix/Hystrix/pull/825) Only check bucket properties at HystrixRollingNumber construction +* [Pull 824](https://github.com/Netflix/Hystrix/pull/824) Only check bucket properties at HystrixRollingPercentile construction +* [Pull 823](https://github.com/Netflix/Hystrix/pull/823) Fix half hidden mean metric in Hystrix Dashboard because of container height +* [Pull 818](https://github.com/Netflix/Hystrix/pull/818) Only check maxQueueSize value at thread pool construction + +### Version 1.4.11 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.11%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.11/)) ### + +* [Pull 814](https://github.com/Netflix/Hystrix/pull/814) Upgrade to RxJava 1.0.12 +* [Pull 813](https://github.com/Netflix/Hystrix/pull/813) Output something when Hystrix falls back on recoverable java.lang.Error +* [Pull 812](https://github.com/Netflix/Hystrix/pull/812) Fixing overload functions in hystrix-clj +* [Pull 808](https://github.com/Netflix/Hystrix/pull/808) Update Hystrix Metrics Stream README + +### Version 1.4.10 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.10%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.10/)) ### + +* [Pull 804](https://github.com/Netflix/Hystrix/pull/804) Fix memory leak by switching back to AtomicIntegerArray for HystrixRollingPercentile + +### Version 1.4.9 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.9%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.9/)) ### + +* [Pull 799](https://github.com/Netflix/Hystrix/pull/799) Fix thread-safety of writes to HystrixRollingPercentile + +### Version 1.4.8 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.8%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.8/)) ### + +* [Pull 797](https://github.com/Netflix/Hystrix/pull/797) Move all histogram reads to a single-threaded path. +* [Pull 794](https://github.com/Netflix/Hystrix/pull/794) Allow dashboard connection on 'Enter' keypress +* [Pull 787](https://github.com/Netflix/Hystrix/pull/787) Reject requests after metrics-event-stream servlet shutdown +* [Pull 785](https://github.com/Netflix/Hystrix/pull/785) Update metrics package from com.codahale.metrics to io.dropwizard.metrics + +### Version 1.4.7 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.7%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.7/)) ### + +* [Pull 783](https://github.com/Netflix/Hystrix/pull/783) Upgrade to RxJava 1.0.10 +* [Pull 781](https://github.com/Netflix/Hystrix/pull/781) Shorten collapser stress test to avoid OOM in Travis +* [Pull 780](https://github.com/Netflix/Hystrix/pull/780) Allow hooks to throw exceptions and not corrupt internal Hystrix state +* [Pull 779](https://github.com/Netflix/Hystrix/pull/779) Use HdrHistogram for capturing latencies +* [Pull 778](https://github.com/Netflix/Hystrix/pull/778) Run jmh using more forks and fewer iterations/fork +* [Pull 776](https://github.com/Netflix/Hystrix/pull/776) Add Bad requests to Hystrix dashboard +* [Pull 775](https://github.com/Netflix/Hystrix/pull/775) Add counters for number of commands, thread pools, groups +* [Pull 774](https://github.com/Netflix/Hystrix/pull/774) Add global concurrent Hystrix threads counter + +### Version 1.4.6 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.6%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.6/)) ### + +* [Pull 772](https://github.com/Netflix/Hystrix/pull/772) Add try-catch to all hook invocations +* [Pull 773](https://github.com/Netflix/Hystrix/pull/773) Move threadPool field to end of metrics stream +* [Pull 770](https://github.com/Netflix/Hystrix/pull/770) Fix AbstractCommand.isCircuitBreakerOpen() return value when circuit is forced open or closed +* [Pull 769](https://github.com/Netflix/Hystrix/pull/769) Add threadPool to command metrics in event stream +* [Pull 767](https://github.com/Netflix/Hystrix/pull/767) JMH upgrade and multithreaded benchmark + +### Version 1.4.5 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.5%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.5/)) ### + +* [Pull 764](https://github.com/Netflix/Hystrix/pull/764) Upgrade RxJava from 1.0.7 to 1.0.9 +* [Pull 763](https://github.com/Netflix/Hystrix/pull/763) Upgrade Jackson for hystrix-metrics-event-stream from 1.9.2 to 2.5.2 +* [Pull 760](https://github.com/Netflix/Hystrix/pull/760) Set Hystrix-created threads to be daemon +* [Pull 757](https://github.com/Netflix/Hystrix/pull/757) Update RxNetty version in hystrix-rx-netty-metrics-stream from 0.3.8 to 0.4.7 +* [Pull 755](https://github.com/Netflix/Hystrix/pull/755) Improve Javadoc for HystrixThreadPoolProperties.Setter +* [Pull 754](https://github.com/Netflix/Hystrix/pull/754) Only fire onFallbackStart/onFallbackError hooks when a user-supplied fallback is invoked +* [Pull 753](https://github.com/Netflix/Hystrix/pull/753) Add timeout to dashboard for semaphore commands +* [Pull 750](https://github.com/Netflix/Hystrix/pull/750) First pass at jmh performance benchmarking +* [Pull 748](https://github.com/Netflix/Hystrix/pull/748) Fix return value of HystrixCircuiBreakerImpl.isOpen when it loses a race to open a circuit +* [Pull 746](https://github.com/Netflix/Hystrix/pull/746) Improve Javadoc for HystrixCommandProperties.Setter + + +### Version 1.4.4 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.4%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.4/)) ### + +* [Pull 743](https://github.com/Netflix/Hystrix/pull/743) Proper Javadoc deprecation for command timeouts being thread-specific +* [Pull 742](https://github.com/Netflix/Hystrix/pull/742) Add flag to disable command timeouts +* [Pull 741](https://github.com/Netflix/Hystrix/pull/741) Bugfix to java.lang.Error handling +* [Pull 735](https://github.com/Netflix/Hystrix/pull/735) (Javanica) BatchHystrixCommand +* [Pull 739](https://github.com/Netflix/Hystrix/pull/739) Mark some java.lang.Errors as unrecoverable and never trigger fallback +* [Pull 738](https://github.com/Netflix/Hystrix/pull/738) Filter out thread pools with no thread activity from hystrics-metrics-event-stream +* [Pull 732](https://github.com/Netflix/Hystrix/pull/732) Comment out flaky unit test + +### Version 1.4.3 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.3%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.3/)) ### + +* [Pull 731](https://github.com/Netflix/Hystrix/pull/731) Revert to Java 6 +* [Pull 728](https://github.com/Netflix/Hystrix/pull/728) Add semaphore-rejected count to dashboard +* [Pull 711](https://github.com/Netflix/Hystrix/pull/711) Use Archaius for plugin registration +* [Pull 671](https://github.com/Netflix/Hystrix/pull/671) Stop passing Transfer-Encoding header when streaming metrics + +### Version 1.4.2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.2%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.2/)) ### + +* [Pull 723](https://github.com/Netflix/Hystrix/pull/723) Fixed Javanica issue where annotation appeared in superclasses +* [Pull 727](https://github.com/Netflix/Hystrix/pull/727) Fixed TravisCI issue by raising timeout in fallback rejection unit test +* [Pull 724](https://github.com/Netflix/Hystrix/pull/724) Fixed backwards-incompatibility where using a custom HystrixConcurrencyStrategy forced use of a non-null HystrixRequestContext +* [Pull 717](https://github.com/Netflix/Hystrix/pull/717) Added error message to dashboard HTML when connection to metrics source fails + +### Version 1.4.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.1%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.1/)) ### + +* [Pull 716](https://github.com/Netflix/Hystrix/pull/716) Fixed backwards-incompatibility where .execute(), .queue(), .observe(), .toObservable() were all made final in 1.4.0 + +### Version 1.4.0 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.0/)) ### + +This version adds HystrixObservableCommand and implements both it and HystrixCommand in terms of [Observables](https://github.com/ReactiveX/RxJava). + +A HystrixObservableCommand allows for fully non-blocking commands that can be composed as part of a larger Observable chain. See [the wiki](https://github.com/Netflix/Hystrix/wiki/How-To-Use#reactive-commands) for more details on usage. Here's an example (using Java 8): + +```java +public class ObservableHttpCommand extends HystrixObsverableCommand { + +@Override +protected Observable construct() { + return httpClient.submit(HttpClientRequest.createGet("/mock.json?numItems=" + numItems)) + .flatMap((HttpClientResponse r) -> r.getContent() + .map(b -> BackendResponse.fromJson(new ByteBufInputStream(b)))); + } + +@Override +protected Observable resumeWithFallback() { + return Observable.just(new BackendResponse(0, numItems, new String[] {})); +}} + +``` + +Because an Observable represents a stream of data, your HystrixObservableCommand may now return a stream of data, and supply a stream of data as a fallback. The methods to do so are `construct()` and `resumeWithFallback()`, respectively. All other aspects of the Hystrix state machine work the same in a HystrixCommand. See [this wiki page](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow-chart) for a diagram of this state machine. + +The public API of HystrixCommand is unchanged, though the internals have significantly changed. Some bugfixes are now possible that affect Hystrix semantics: + +* Timeouts now apply to semaphore-isolated commands as well as thread-isolated commands. Before 1.4.x, semaphore-isolated commands could not timeout. They now have a timeout registered on another (HystrixTimer) thread, which triggers the timeout flow. If you use semaphore-isolated commands, they will now see timeouts. As all HystrixCommands have a [default timeout](https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.thread.timeoutInMilliseconds), this potentially affects all semaphore-isolated commands. +* Timeouts now fire on `HystrixCommand.queue()`, even if the caller never calls `get()` on the resulting Future. Before 1.4.x, only calls to `get()` triggered the timeout mechanism to take effect. + +You can see more in-depth examples of how the new functionality of Hystrix 1.4 is used at [Netflix/ReactiveLab](https://github.com/Netflix/ReactiveLab). + +You can learn more about Observables and RxJava at [Netflix/RxJava](https://github.com/ReactiveX/RxJava). + +As this was a major refactoring of Hystrix internals, we (Netflix) have run a release candidate of Hystrix 1.4 in canaries and production over the last month to gain confidence. + +* [Pull 701](https://github.com/Netflix/Hystrix/pull/701) Fix Javadoc warnings +* [Pull 700](https://github.com/Netflix/Hystrix/pull/700) Example code for publishing to Graphite + +### Version 1.4.0 Release Candidate 9 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-rc.9%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.0-rc.9/)) ### +_NOTE: This code is believed to be production worthy. As of now, there are no known bugs preventing this becoming 1.4.0. Please report any design issues/questions, bugs, or any observations about this release to the [Issues](https://github.com/Netflix/Hystrix/issues) page._ + +* [Pull 697](https://github.com/Netflix/Hystrix/pull/697) Add execution event for FALLBACK_REJECTION and unit tests +* [Pull 696](https://github.com/Netflix/Hystrix/pull/696) Upgrade to RxJava 1.0.7 +* [Pull 694](https://github.com/Netflix/Hystrix/pull/694) Make execution timeout in HystrixCommandProperties work in the case when classes extend HystrixCommandProperties +* [Pull 693](https://github.com/Netflix/Hystrix/pull/693) Hystrix Dashboard sorting issue + +### Version 1.4.0 Release Candidate 8 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-rc.8%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.0-rc.8/)) ### +_NOTE: This code is believed to be production worthy. As of now, there are no known bugs preventing this becoming 1.4.0. Please report any design issues/questions, bugs, or any observations about this release to the [Issues](https://github.com/Netflix/Hystrix/issues) page._ + +* [Pull 691](https://github.com/Netflix/Hystrix/pull/691) HystrixCommandTest test with large number of semaphores +* [Pull 690](https://github.com/Netflix/Hystrix/pull/690) Add back ExceptionThreadingUtility +* [Pull 688](https://github.com/Netflix/Hystrix/pull/688) Add metrics for EMIT and FALLBACK_EMIT +* [Pull 687](https://github.com/Netflix/Hystrix/pull/687) Fixed issue where fallback rejection was also incrementing fallback failure metric +* [Pull 686](https://github.com/Netflix/Hystrix/pull/686) HystrixCommandTest Unit test refactoring +* [Pull 683](https://github.com/Netflix/Hystrix/pull/683) Add and Deprecate pieces of execution hook API to be more consistent +* [Pull 681](https://github.com/Netflix/Hystrix/pull/681) Add cache hit execution hook +* [Pull 680](https://github.com/Netflix/Hystrix/pull/680) Add command rejection metrics for HystrixThreadPools + + +### Version 1.4.0 Release Candidate 7 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-rc.7%22), [Bintray](https://bintray.com/netflixoss/maven/Hystrix/1.4.0-rc.7/)) ### +NOTE: This code is believed to be production worthy. As of now, there are no known bugs preventing this becoming 1.4.0. Please report any design issues/questions, bugs, or any observations about this release to the [Issues](https://github.com/Netflix/Hystrix/issues) page + +* [Pull 678](https://github.com/Netflix/Hystrix/pull/678) Fix current concurrent execution count +* [Pull 676](https://github.com/Netflix/Hystrix/pull/676) Add test to confirm that bad requests do not affect circuit breaker's computed error percentage +* [Pull 675](https://github.com/Netflix/Hystrix/pull/675) Deprecate method names for executionTimeout that are thread-specific +* [Pull 672](https://github.com/Netflix/Hystrix/pull/672) Limit the thread-interrupt behavior to occur only on timeouts +* [Pull 669](https://github.com/Netflix/Hystrix/pull/669) Added unit tests to demonstrate non-blocking semaphore timeout +* [Pull 667](https://github.com/Netflix/Hystrix/pull/667) Added rolling max counter for command execution +* [Pull 666](https://github.com/Netflix/Hystrix/pull/666) Added missing licenses +* [Pull 665](https://github.com/Netflix/Hystrix/pull/665) Added comment to HystrixConcurrencyStrategy about non-idempotency of strategy application +* [Pull 647](https://github.com/Netflix/Hystrix/pull/647) Tie command property to thread interrupt +* [Pull 645](https://github.com/Netflix/Hystrix/pull/645) Remove incorrect reference to async timeout +* [Pull 644](https://github.com/Netflix/Hystrix/pull/644) Add RequestCollapser metrics to Yammer Metrics Publisher +* [Pull 643](https://github.com/Netflix/Hystrix/pull/643) Stress-test HystrixObservalbeCollapser +* [Pull 642](https://github.com/Netflix/Hystrix/pull/642) Fix flakiness of HystrixObservableCommandTest.testRejectedViaSemaphoreIsolation +* [Pull 641](https://github.com/Netflix/Hystrix/pull/641) Fix flakiness of testSemaphorePermitsInUse +* [Pull 608](https://github.com/Netflix/Hystrix/pull/608) Make HystrixObservableCommand handle both sync and async exceptions +* [Pull 607](https://github.com/Netflix/Hystrix/pull/607) Upgrade RxJava from 1.0.4 to 1.0.5 +* [Pull 604](https://github.com/Netflix/Hystrix/pull/604) Added EMIT and FALLBACK_EMIT event types that get emitted in HystrixObservableCommand +* [Pull 599](https://github.com/Netflix/Hystrix/pull/599) Added metrics to HystrixObservableCollapser +* [Pull 596](https://github.com/Netflix/Hystrix/pull/596) Fixed HystrixContextScheduler to conform with RxJava Worker contract +* [Pull 583](https://github.com/Netflix/Hystrix/pull/583) Style and consistency fixes +* [Pull 582](https://github.com/Netflix/Hystrix/pull/582) Add more unit tests for non-blocking HystrixCommand.queue() +* [Pull 580](https://github.com/Netflix/Hystrix/pull/580) Unit test to demonstrate fixed non-blocking timeout for HystrixCommand.queue() +* [Pull 579](https://github.com/Netflix/Hystrix/pull/579) Remove synchronous timeout +* [Pull 577](https://github.com/Netflix/Hystrix/pull/577) Upgrade language level to Java7 +* [Pull 576](https://github.com/Netflix/Hystrix/pull/576) Add request collapser metrics +* [Pull 573](https://github.com/Netflix/Hystrix/pull/573) Fix link to CHANGELOG.md in README.md +* [Pull 572](https://github.com/Netflix/Hystrix/pull/572) Add bad request metrics at the command granularity +* [Pull 567](https://github.com/Netflix/Hystrix/pull/567) Comment out flaky unit-tests +* [Pull 566](https://github.com/Netflix/Hystrix/pull/566) Fix hardcoded groupname in CodaHale metrics publisher +* [Commit 6c08d9](https://github.com/Netflix/Hystrix/commit/6c08d90fd10a947bdbee81afc9c0f866d1f33eef) Bumped nebula.netflixoss from 2.2.3 to 2.2.5 +* [Pull 562](https://github.com/Netflix/Hystrix/pull/562) Build changes to nebula.netflixoss (initially submitted as [Pull 469](https://github.com/Netflix/Hystrix/pull/469)) + + +### Version 1.4.0 Release Candidate 6 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-RC6%22)) ### + +_NOTE: This code is believed to be production worthy but is still a "Release Candidate" until [these possible functional or design issues are resolved](https://github.com/Netflix/Hystrix/issues?q=is%3Aopen+is%3Aissue+milestone%3A1.4.0-RC7)._ +* [Pull 534](https://github.com/Netflix/Hystrix/pull/534) Bump RxJava to 1.0.4 +* [Pull 532](https://github.com/Netflix/Hystrix/pull/532) Hystrix-Clojure: Fix typo +* [Pull 531](https://github.com/Netflix/Hystrix/pull/531) Move onThreadStart execution hook after check that wrapping thread timed out +* [Pull 530](https://github.com/Netflix/Hystrix/pull/530) Add shutdown hook to metrics servlet for WebSphere +* [Pull 527](https://github.com/Netflix/Hystrix/pull/527) Creating a synthetic exception in the semaphore execution and short-circuited case +* [Pull 526](https://github.com/Netflix/Hystrix/pull/526) Move onRunSuccess/onRunError and thread-pool book-keeping to Hystrix thread +* [Pull 524](https://github.com/Netflix/Hystrix/pull/524) Change calls from getExecutedCommands() to getAllExecutedCommands() +* [Pull 516](https://github.com/Netflix/Hystrix/pull/516) Updated HystrixServoMetricsPublisher initalization of singleton +* [Pull 489](https://github.com/Netflix/Hystrix/pull/489) Javanica: Request Caching +* [Pull 512](https://github.com/Netflix/Hystrix/pull/512) Add execution hook Javadoc +* [Pull 511](https://github.com/Netflix/Hystrix/pull/511) Fix missing onComplete hook call when command short-circuits and missing onRunSuccess hook call in thread-timeout case +* [Pull 466](https://github.com/Netflix/Hystrix/pull/466) Add unit tests to HystrixCommand and HystrixObservableCommand +* [Pull 465](https://github.com/Netflix/Hystrix/pull/465) Handle error in construction of HystrixMetricsPoller +* [Pull 457](https://github.com/Netflix/Hystrix/pull/457) Fixing resettability of HystrixMetricsPublisherFactory +* [Pull 451](https://github.com/Netflix/Hystrix/pull/451) Removed ExceptionThreadingUtility +* [Pull 366](https://github.com/Netflix/Hystrix/pull/366) Added support to get command start time in Nanos +* [Pull 456](https://github.com/Netflix/Hystrix/pull/456) Allow hooks to generate HystrixBadRequestExceptions that get handled appropriately +* [Pull 454](https://github.com/Netflix/Hystrix/pull/454) Add tests around HystrixRequestLog in HystrixObservableCommand +* [Pull 453](https://github.com/Netflix/Hystrix/pull/453) Resettable command and thread pool defaults +* [Pull 452](https://github.com/Netflix/Hystrix/pull/452) Make HystrixPlugins resettable +* [Pull 450](https://github.com/Netflix/Hystrix/pull/450) Add fallback tests to HystrixObservableCommand +* [Pull 449](https://github.com/Netflix/Hystrix/pull/449) Move thread completion bookkeeping to end of chain +* [Pull 447](https://github.com/Netflix/Hystrix/pull/447) Synchronous queue fix +* [Pull 376](https://github.com/Netflix/Hystrix/pull/376) Javanica README cleanup +* [Pull 378](https://github.com/Netflix/Hystrix/pull/378) Exection hook call sequences (based on work submitted in [Pull 327](https://github.com/Netflix/Hystrix/pull/327)) +* [Pull 374](https://github.com/Netflix/Hystrix/pull/374) RequestBatch logging +* [Pull 371](https://github.com/Netflix/Hystrix/pull/371) Defer creation of IllegalStateException in collapser flow (based on work submitted in [Pull 264](https://github.com/Netflix/Hystrix/pull/264)) +* [Pull 369](https://github.com/Netflix/Hystrix/pull/369) Added basic auth to Hystrix Dashboard (based on work submitted in [Pull 336](https://github.com/Netflix/Hystrix/pull/336)) +* [Pull 367](https://github.com/Netflix/Hystrix/pull/367) Added thread pool metrics back to execution flow (based on test submitted in [Pull 339](https://github.com/Netflix/Hystrix/pull/339)) +* [Pull 365](https://github.com/Netflix/Hystrix/pull/365) Fix Javadoc for HystrixCommand.Setter +* [Pull 364](https://github.com/Netflix/Hystrix/pull/364) Upgrade servo to 0.7.5 +* [Pull 362](https://github.com/Netflix/Hystrix/pull/362) Fixed hystrix-rxnetty-metrics-stream unit tests +* [Pull 359](https://github.com/Netflix/Hystrix/pull/359) Fixed Javanica unit tests +* [Pull 361](https://github.com/Netflix/Hystrix/pull/361) Race condition when creating HystrixThreadPool (initially submitted as [Pull 270](https://github.com/Netflix/Hystrix/pull/270)) +* [Pull 358](https://github.com/Netflix/Hystrix/pull/358) Fixed Clojure unit tests that failed with RxJava 1.0 +* [Commit 2edcd5](https://github.com/Netflix/Hystrix/commit/2edcd578194849a1c2f5acd73a2e6f10ebfdd112) Upgrade to core-metrics 3.0.2 +* [Pull 310](https://github.com/Netflix/Hystrix/pull/310) Osgi-ify hystrix-core, hystrix-examples +* [Pull 340](https://github.com/Netflix/Hystrix/pull/340) Race condition when creating HystrixThreadPool +* [Pull 343](https://github.com/Netflix/Hystrix/pull/343) Added 3 new constructors for common command setup +* [Pull 347](https://github.com/Netflix/Hystrix/pull/347) Javanica: Allow for @HystrixCommand to be used on parameterized return type +* [Pull 353](https://github.com/Netflix/Hystrix/pull/353) Fixing a hystrix-examples compilation failure +* [Pull 338](https://github.com/Netflix/Hystrix/pull/338) API Changes after design review of [Issue 321](https://github.com/Netflix/Hystrix/issues/321) +* [Pull 344](https://github.com/Netflix/Hystrix/pull/344) Upgrade to Gradle 1.12 +* [Pull 334](https://github.com/Netflix/Hystrix/pull/334) Javanica: Hystrix Error Propagation +* [Pull 326](https://github.com/Netflix/Hystrix/pull/326) Javanica: Added support for setting threadPoolProperties through @HystrixCommand annotation +* [Pull 318](https://github.com/Netflix/Hystrix/pull/318) HystrixAsyncCommand and HystrixObservableCommand +* [Pull 316](https://github.com/Netflix/Hystrix/pull/316) Add support for execution.isolation.semaphore.timeoutInMilliseconds + +### Version 1.3.20 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.20%22)) ### +* [Pull 533] (https://github.com/Netflix/Hystrix/pull/533) Upgrade to RxJava 1.0.4 +* [Pull 528] (https://github.com/Netflix/Hystrix/pull/528) Pass RuntimeException to onError hook in semaphore-rejection and short-circuit cases, instead of null +* [Pull 520] (https://github.com/Netflix/Hystrix/pull/520) More unit tests for hook ordering +* [Commit 61b77c] (https://github.com/Netflix/Hystrix/commit/61b77c305bda6dbd4dc8c86445b4a6670f981845) Fix flow where ExecutionHook.onComplete was called twice +* [Commit a5e52a] (https://github.com/Netflix/Hystrix/commit/a5e52a6d29cd911c1e14ec107a875a9343472db5) Add call to ExecutionHook.onError in HystrixBadRequestException flow +* [Commit cec25e] (https://github.com/Netflix/Hystrix/commit/cec25ed7c6f10c4c59189b443bda844fa39043d6) Fix hook ordering assertions +* [Pull 508] (https://github.com/Netflix/Hystrix/pull/508) Backport of [Pull 327] (https://github.com/Netflix/Hystrix/pull/327) from master: Add hook assertions to unit tests +* [Pull 507] (https://github.com/Netflix/Hystrix/pull/507) Fix hystrix-clj unit tests +* [Commit 62be49] (https://github.com/Netflix/Hystrix/commit/62be49465ae418509814fd195081ef7611eb6015) RxJava 1.0.2 + +### Version 1.3.19 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.19%22)) ### + +* [Pull 348](https://github.com/Netflix/Hystrix/pull/348) Javanica: Allow for @HystrixCommand to be used on parameterized return type +* [Pull 329](https://github.com/Netflix/Hystrix/pull/329) Javanica: allowing configuration of threadPoolProperties through @HystrixCommand annotation + +### Version 1.4.0 Release Candidate 5 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-RC5%22)) ### + +_NOTE: This code is believed to be production worthy but is still a "Release Candidate" until [these possible functional or design issues are resolved](https://github.com/Netflix/Hystrix/issues?q=is%3Aopen+is%3Aissue+milestone%3A1.4)._ + +* [Pull 314](https://github.com/Netflix/Hystrix/pull/314) RxJava 0.20 and Remove Deprecated Usage +* [Pull 307](https://github.com/Netflix/Hystrix/pull/307) Dashboard: Avoid NPE when 'origin' parameter not present + +### Version 1.3.18 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.18%22)) ### + +* [Pull 305](https://github.com/Netflix/Hystrix/pull/305) Removing deprecated RxJava usage + +### Version 1.3.17 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.17%22)) ### + +* [Pull 291](https://github.com/Netflix/Hystrix/pull/291) String optimization for HystrixRequestLog +* [Pull 296](https://github.com/Netflix/Hystrix/pull/296) Fix Premature Unsubscribe Bug +* [Commit 47122e](https://github.com/Netflix/Hystrix/commit/1268454ec4381b6ae121cac1675205484847122e) RxJava 0.20.1 + +### Version 1.4.0 Release Candidate 4 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-RC4%22)) ### + +_NOTE: This code is NOT considered production worthy yet, hence the "Release Candidate" status._ + +This fixes some bugs and changes the `HystrixObservableCollapser` signature to support `Observable` batches. + +* [Pull 256](https://github.com/Netflix/Hystrix/pull/256) Fix Race Condition on Timeout +* [Pull 261](https://github.com/Netflix/Hystrix/pull/261) New signature for HystrixObservableCollapser +* [Pull 254](https://github.com/Netflix/Hystrix/pull/254) Remove jsr305 Dependency +* [Pull 260](https://github.com/Netflix/Hystrix/pull/260) RxJava 0.18.2 +* [Pull 253](https://github.com/Netflix/Hystrix/pull/253) Handling InterruptedExceptions in the HystrixMetricsStreamServlet + +### Version 1.4.0 Release Candidate 3 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-RC3%22)) ### + +_NOTE: This code is NOT considered production worthy yet, hence the "Release Candidate" status._ + +This adds non-blocking support to the collapser via the `HystrixObservableCollapser` type, fixes some bugs and upgrades to RxJava 0.18. + +* [Pull 245](https://github.com/Netflix/Hystrix/pull/245) HystrixObservableCollapser +* [Pull 246](https://github.com/Netflix/Hystrix/pull/246) RxJava 0.18 +* [Pull 250](https://github.com/Netflix/Hystrix/pull/250) Tripped CircuitBreaker Wouldn't Close Under Contention +* [Pull 243](https://github.com/Netflix/Hystrix/pull/243) Update servo to 0.6 +* [Pull 240](https://github.com/Netflix/Hystrix/pull/240) Add missing reset for CommandExecutionHook in HystrixPlugins.UnitTest +* [Pull 244](https://github.com/Netflix/Hystrix/pull/244) Javanica: Cleaner error propagation + + +### Version 1.4.0 Release Candidate 2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-RC2%22)) ### + +_NOTE: This code is NOT considered production worthy yet, hence the "Release Candidate" status._ + +This fixes a bug found in Release Candidate 1 that caused the semaphore limits to be applied when thread isolation was chosen. + +It also stops scheduling callbacks onto new threads and lets the Hystrix threads perform the callbacks. + +* [Pull 238](https://github.com/Netflix/Hystrix/pull/238) Fix for Semaphore vs Thread Isolation Bug +* [Pull 230](https://github.com/Netflix/Hystrix/pull/230) Javanica Module: Added support for Request Cache and Reactive Execution + + +### Version 1.4.0 Release Candidate 1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.4.0-RC1%22)) ### + +This is the first release candidate of 1.4.0 that includes `HystrixObservableCommand` ([Source](https://github.com/Netflix/Hystrix/blob/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java)) that supports bulkheading asynchronous, non-blocking sources. + +_NOTE: This code is NOT considered production worthy yet, hence the "Release Candidate" status._ + +_It has run for 1 day on a single machine taking production traffic at Netflix. This is sufficient for us to proceed to a release candidate for official canary testing, but we intend to test for a week or two before doing a final release._ + +Here is a very basic example using Java 8 to make an HTTP call via Netty and receives a stream of chunks back: + +```java + + public static void main(String args[]) { + HystrixObservableCommand command = bulkheadedNetworkRequest("www.google.com"); + command.toObservable() + // using BlockingObservable.forEach for demo simplicity + .toBlockingObservable().forEach(d -> System.out.println(d)); + System.out.println("Time: " + command.getExecutionTimeInMilliseconds() + + " Events: " + command.getExecutionEvents()); + } + + public static HystrixObservableCommand bulkheadedNetworkRequest(final String host) { + return new HystrixObservableCommand(HystrixCommandGroupKey.Factory.asKey("http")) { + + @Override + protected Observable run() { + return directNetworkRequest(host); + } + + @Override + protected Observable getFallback() { + return Observable.just("Error 500"); + } + + }; + } + + public static Observable directNetworkRequest(String host) { + return RxNetty.createHttpClient(host, 80) + .submit(HttpClientRequest.createGet("/")) + .flatMap(response -> response.getContent()) + .map(data -> data.toString(Charset.defaultCharset())); + } +``` + +* [Pull 218](https://github.com/Netflix/Hystrix/pull/218) Hystrix 1.4 - Async/Non-Blocking +* [Pull 217](https://github.com/Netflix/Hystrix/pull/217) Javanica: Added support for "Reactive Execution" and "Error Propagation" +* [Pull 219](https://github.com/Netflix/Hystrix/pull/219) Restore HystrixContext* Constructors without ConcurrencyStrategy + +### Version 1.3.16 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.16%22)) ### + +* [Pull 258](https://github.com/Netflix/Hystrix/pull/258) Skip CachedObservableOriginal when no getCacheKey() +* [Pull 259](https://github.com/Netflix/Hystrix/pull/259) Fix #257 with RxJava 0.18.2 + + +### Version 1.3.15 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.15%22)) ### + +* [Pull 248](https://github.com/Netflix/Hystrix/pull/248) RxJava 0.18 for Hystrix +* [Pull 249](https://github.com/Netflix/Hystrix/pull/249) Tripped CircuitBreaker Wouldn't Close Under Contention +* [Pull 229](https://github.com/Netflix/Hystrix/pull/229) Javanica: Added support for Request Cache and Reactive Execution +* [Pull 242](https://github.com/Netflix/Hystrix/pull/242) Javanica: Cleaner error propagation + + +### Version 1.3.14 (not released) ### + +* [Pull 228](https://github.com/Netflix/Hystrix/pull/228) Upgrade 1.3.x to RxJava 0.17.1 + + +### Version 1.3.13 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.13%22)) ### + + +* [Pull 216](https://github.com/Netflix/Hystrix/pull/216) hystrix-javanica contrib-module: [annotation support](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica) + + +### Version 1.3.12 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.12%22)) ### + +* [Pull 214](https://github.com/Netflix/Hystrix/pull/214) HystrixContextCallable/Runnable Constructors + +### Version 1.3.11 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.11%22)) ### + +* We'll ignore this release ever happened. Exact same binary as 1.3.10. (It helps to push code to Github before releasing.) + +### Version 1.3.10 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.10%22)) ### + +* [Pull 211](https://github.com/Netflix/Hystrix/pull/211) Update Javassist version to 3.18.1-GA +* [Pull 213](https://github.com/Netflix/Hystrix/pull/213) BugFix: Timeout does not propagate request context +* [Pull 204](https://github.com/Netflix/Hystrix/pull/204) deploying hystrix dashboard on tomcat, SLF4J complains about a missing implementation +* [Pull 199](https://github.com/Netflix/Hystrix/pull/199) Add new module for publishing metrics to Coda Hale Metrics version 3 + +### Version 1.3.9 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.9%22)) ### + +* [Pull 210](https://github.com/Netflix/Hystrix/pull/210) HystrixContextScheduler was not wrapping the Inner Scheduler +* [Pull 206](https://github.com/Netflix/Hystrix/pull/206) Bugfix ExceptionThreadingUtility +* [Pull 203](https://github.com/Netflix/Hystrix/pull/203) Made HystrixTimer initialization thread-safe + +### Version 1.3.8 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.8%22)) ### + +* [Pull 194](https://github.com/Netflix/Hystrix/pull/194) BugFix: Do not overwrite user thread duration in case of timeouts + +### Version 1.3.7 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.7%22)) ### + +* [Pull 189](https://github.com/Netflix/Hystrix/pull/189) BugFix: Race condition between run() and timeout +* [Pull 190](https://github.com/Netflix/Hystrix/pull/190) BugFix: ConcurrencyStrategy.wrapCallable was not being used on callbacks + + +### Version 1.3.6 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.6%22)) ### + +* [Pull 181](https://github.com/Netflix/Hystrix/pull/181) [hystrix-contrib/hystrix-clj] Making Command keys quantified by namespaces +* [Pull 182](https://github.com/Netflix/Hystrix/pull/182) Removing unused Legend component latent. Removed from html templates/css +* [Pull 183](https://github.com/Netflix/Hystrix/pull/183) Bugfix to HystrixBadRequestException handling +* [Pull 184](https://github.com/Netflix/Hystrix/pull/184) BugFix: queue() BadRequestException Handling on Cached Response +* [Pull 185](https://github.com/Netflix/Hystrix/pull/185) BugFix: Observable.observeOn Scheduler Lost RequestContext +* [0fb0d3d](https://github.com/Netflix/Hystrix/commit/0fb0d3d25e406f8b6240d312c2ee1f515c77fc13) RxJava 0.14 + +### Version 1.3.5 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.5%22)) ### + +* [Pull 179](https://github.com/Netflix/Hystrix/pull/179) RxJava 0.13 + +### Version 1.3.4 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.4%22)) ### + +* [f68fa23c](https://github.com/Netflix/Hystrix/commit/bd6dfac5255753978253605f7e8b4c6af68fa23c) RxJava [0.11,0.12) + +### Version 1.3.3 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.3%22)) ### + +* [858e334f](https://github.com/Netflix/Hystrix/commit/7bcf0ee7b876cbfdcb942ea83637d4b5858e334f) RxJava 0.11 + +### Version 1.3.2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.2%22)) ### + +* [Pull 173](https://github.com/Netflix/Hystrix/pull/173) Fix Exception vs Throwable typo in preparation for RxJava 0.11.0 + +### Version 1.3.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.1%22)) ### + +* [Pull 170](https://github.com/Netflix/Hystrix/pull/170) Add rx support to hystrix-clj + +### Version 1.3.0 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.3.0%22)) ### + +This version integrations Hystrix with [RxJava](https://github.com/Netflix/RxJava) to enable non-blocking reactive execution and functional composition. + +Async execution can now be done reactively with the `observe()` method and it will callback when the value is received: + +```java +Observable s = new CommandHelloWorld("World").observe(); +``` + +A simple example of subscribing to the value (using a Groovy lambda instead of anonymous inner class): + +```groovy +s.subscribe({ value -> println(value) }) +``` + +A "Hello World" example of reactive execution can be [found on the wiki](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Reactive-Execution). + +More can be learned about RxJava and the composition features at https://github.com/Netflix/RxJava/wiki + +This release is a major refactoring of the Hystrix codebase. To assert correctness and performance it was run in production canary servers on the Netflix API several times during development and for over a week during release candidate stages. Prior to this release the 1.3.0.RC1 version has been running in full Netflix API production for several days performing billions of executions a day. + +* [Pull 151](https://github.com/Netflix/Hystrix/pull/151) Version 1.3 - RxJava Observable Integration +* [Pull 158](https://github.com/Netflix/Hystrix/pull/158) Expose current HystrixCommand to fns + + +### Version 1.2.18 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.18%22)) ### + +* [Pull 152](https://github.com/Netflix/Hystrix/pull/152) Escape meta-characters to fix dashboard +* [Pull 156](https://github.com/Netflix/Hystrix/pull/156) Improve hystrix-clj docs +* [Pull 155](https://github.com/Netflix/Hystrix/pull/155) Reset Hystrix after hystrix-clj tests have run + +### Version 1.2.17 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.17%22)) ### + +* [Pull 138](https://github.com/Netflix/Hystrix/pull/138) Eclipse and IDEA Config +* [Pull 141](https://github.com/Netflix/Hystrix/pull/141) Upgrade Clojuresque (hystrix-clj builds) +* [Pull 139](https://github.com/Netflix/Hystrix/pull/139) Fix dashboard math bug on thread pool rate calculations +* [Pull 149](https://github.com/Netflix/Hystrix/pull/149) Allow getFallback to query failure states + +### Version 1.2.16 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.16%22)) ### + +* [Pull 132](https://github.com/Netflix/Hystrix/pull/132) Add `with-context` macro for conviently wrapping collapsers in thier own context +* [Pull 136](https://github.com/Netflix/Hystrix/pull/136) Fixed the mock stream +* [Pull 137](https://github.com/Netflix/Hystrix/pull/137) Limit scope of CurrentThreadExecutingCommand + +### Version 1.2.15 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.15%22)) ### + +This is fixing a bug introduced in the last release that affects semaphore isolated commands that use request caching. + +* [Pull 133](https://github.com/Netflix/Hystrix/pull/133) Fix NoSuchElement Exception + +### Version 1.2.14 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.14%22)) ### + +* [Issue 116](https://github.com/Netflix/Hystrix/issues/116) Mechanism for Auditing Network Access Not Isolated by Hystrix + +A new module for instrumenting network access to identify calls not wrapped by Hystrix. + +See the module README for more information: https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-network-auditor-agent + +### Version 1.2.13 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.13%22)) ### + +* [Issue 127](https://github.com/Netflix/Hystrix/issues/127) Add destroy() method to MetricsServlet + +### Version 1.2.12 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.12%22)) ### + +* [Issue 124](https://github.com/Netflix/Hystrix/issues/124) NPE if Hystrix.reset() called when it's already shutdown + +### Version 1.2.11 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.11%22)) ### + +* [Issue 113](https://github.com/Netflix/Hystrix/issues/113) IllegalStateException: Future Not Started (on thread pool rejection with response caching) +* [Issue 118](https://github.com/Netflix/Hystrix/issues/118) Semaphore counter scope was global instead of per-key +* [Pull 121](https://github.com/Netflix/Hystrix/issues/121) Concurrent execution metric for semaphore and thread isolation + +### Version 1.2.10 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.10%22)) ### + +* [Issue 80](https://github.com/Netflix/Hystrix/issues/80) HystrixCollapser Concurrency and Performance Fixes + +### Version 1.2.9 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.9%22)) ### + +* [Issue 109](https://github.com/Netflix/Hystrix/issues/109) Hystrix.reset() now shuts down HystrixTimer +* [Pull 110](https://github.com/Netflix/Hystrix/issues/110) hystrix-clj cleanup +* [Pull 112](https://github.com/Netflix/Hystrix/issues/112) Further work on HystrixCollapser IllegalStateException ([Issue 80](https://github.com/Netflix/Hystrix/issues/80)) + +### Version 1.2.8 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.8%22)) ### + +* [Issue 102](https://github.com/Netflix/Hystrix/issues/102) Hystrix.reset() functionality for clean shutdown and resource cleanup +* [Pull 103](https://github.com/Netflix/Hystrix/issues/103) hystrix-clj cleanup +* [Pull 104](https://github.com/Netflix/Hystrix/issues/104) javadoc clarification +* [Pull 105](https://github.com/Netflix/Hystrix/issues/104) Added IntelliJ IDEA support, cleanup to Eclipse support + +### Version 1.2.7 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.7%22)) ### + +* [Pull 99](https://github.com/Netflix/Hystrix/issues/99) Experimental Clojure Bindings [hystrix-clj](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-clj) + +### Version 1.2.6 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.6%22)) ### + +* [Issue 96](https://github.com/Netflix/Hystrix/issues/96) Remove 'final' modifiers to allow mocking + +### Version 1.2.5 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.5%22)) ### + +* [Pull 94](https://github.com/Netflix/Hystrix/pull/94) Force character encoding for event stream to utf-8 +* [Issue 60](https://github.com/Netflix/Hystrix/issues/60) Dashboard: Hover for full name (when shortened with ellipsis) +* [Issue 53](https://github.com/Netflix/Hystrix/issues/53) RequestLog: Reduce Chance of Memory Leak + +### Version 1.2.4 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.4%22)) ### + +* [Pull 91](https://github.com/Netflix/Hystrix/pull/91) handle null circuit breaker in HystrixMetricsPoller + +### Version 1.2.3 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.3%22)) ### + +* [Issue 85](https://github.com/Netflix/Hystrix/issues/85) hystrix.stream holds connection open if no metrics +* [Pull 84](https://github.com/Netflix/Hystrix/pull/84) include 'provided' dependencies in Eclipse project classpath + +### Version 1.2.2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.2%22)) ### + +* [Issue 82](https://github.com/Netflix/Hystrix/issues/82) ThreadPool stream should include reportingHosts + +### Version 1.2.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.1%22)) ### + +* [Issue 80](https://github.com/Netflix/Hystrix/issues/80) IllegalStateException: Future Not Started +* [Issue 78](https://github.com/Netflix/Hystrix/issues/78) Include more info when collapsed requests remain in queue + +### Version 1.2.0 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.2.0%22)) ### + +* [Issue 10](https://github.com/Netflix/Hystrix/issues/10) HystrixCommand Execution Hooks via Plugin + * [Pull 71](https://github.com/Netflix/Hystrix/pull/71) Change Throwable to Exception + * [Pull 71](https://github.com/Netflix/Hystrix/pull/71) jettyRun support for running webapps via gradle +* [Issue 15](https://github.com/Netflix/Hystrix/issues/15) Property to disable percentile calculations +* [Issue 69](https://github.com/Netflix/Hystrix/issues/69) Property to disable fallbacks +* [Pull 73](https://github.com/Netflix/Hystrix/pull/73) Make servlet-api a provided dependency +* [Pull 74](https://github.com/Netflix/Hystrix/pull/74) Dashboard problem when using Turbine (Stream not flushing) + +### Version 1.1.7 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20v%3A%221.1.7%22)) ### + +* [Pull 67](https://github.com/Netflix/Hystrix/pull/67) Unit tests for request log and checked exceptions +* [Pull 66](https://github.com/Netflix/Hystrix/pull/66) Making provided scope transtive +* [Pull 65](https://github.com/Netflix/Hystrix/pull/65) Fixed gitignore definition of build output directories +* [Issue 63](https://github.com/Netflix/Hystrix/issues/63) Add "throws Exception" to HystrixCommand run() method +* [Pull 62](https://github.com/Netflix/Hystrix/pull/62) applying js fixes to threadPool ui +* [Pull 61](https://github.com/Netflix/Hystrix/pull/61) Request log with timeouts +* [Issue 55](https://github.com/Netflix/Hystrix/issues/55) HysrixRequestLog: Missing Events and Time on Timeouts +* [Issue 20](https://github.com/Netflix/Hystrix/issues/20) TotalExecutionTime not tracked on queue() +* [Pull 57](https://github.com/Netflix/Hystrix/pull/57) Dashboard js fix +* [Issue 39](https://github.com/Netflix/Hystrix/issues/39) HystrixPlugins Bootstrapping Problem - Race Conditions +* [Pull 52](https://github.com/Netflix/Hystrix/pull/52) Gradle Build Changes + +### Version 1.1.6 ### + +* [Pull 51](https://github.com/Netflix/Hystrix/pull/51) Merging in gradle-template, specifically provided + +### Version 1.1.5 ### + +* [Pull 50](https://github.com/Netflix/Hystrix/pull/50) Make javax.servlet-api a 'provided' dependency not 'compile' + +### Version 1.1.4 ### + +* [Pull 49](https://github.com/Netflix/Hystrix/pull/49) Cleaner design (for metrics) by injecting listener into constructor. + +### Version 1.1.3 ### + +* [Pull 47](https://github.com/Netflix/Hystrix/pull/47) Support pausing/resuming metrics poller +* [Pull 48](https://github.com/Netflix/Hystrix/pull/48) Fixing non-deterministic unit test +* README files added to submodules + +### Version 1.1.2 ### + +* [Pull 44](https://github.com/Netflix/Hystrix/pull/44) Hystrix Dashboard + +### Version 1.1.1 ### + +* [Issue 24](https://github.com/Netflix/Hystrix/issues/24) Yammer Metrics Support +* [Pull 43](https://github.com/Netflix/Hystrix/pull/43) Fix the wrong percentile for latencyExecute_percentile_75 in the Servo publisher + +### Version 1.1.0 ### + +* [Pull 32](https://github.com/Netflix/Hystrix/pull/32) servo-event-stream module +* [Pull 33](https://github.com/Netflix/Hystrix/pull/33) Remove Servo dependency from core, move to submodule +* [Pull 35](https://github.com/Netflix/Hystrix/pull/35) Metrics event stream +* [Issue 34](https://github.com/Netflix/Hystrix/issues/34) Remove Strategy Injection on HystrixCommand +* [Pull 36](https://github.com/Netflix/Hystrix/pull/36) example webapp +* [Pull 37](https://github.com/Netflix/Hystrix/pull/37) Migrate metrics stream from org.json.JSONObject to Jackson + +### Version 1.0.3 ### + +* [Pull 4](https://github.com/Netflix/Hystrix/pull/4) Contrib request context servlet filters +* [Pull 16](https://github.com/Netflix/Hystrix/pull/16) Change logger from info to debug for property changes +* [Issue 12](https://github.com/Netflix/Hystrix/issues/12) Use logger.error not logger.debug for fallback failure +* [Issue 8](https://github.com/Netflix/Hystrix/issues/8) Capture exception from run() and expose getter +* [Issue 22](https://github.com/Netflix/Hystrix/issues/22) Default Collapser scope to REQUEST if using Setter +* [Pull 27](https://github.com/Netflix/Hystrix/pull/27) Initialize HealthCounts to non-null value +* [Issue 28](https://github.com/Netflix/Hystrix/issues/28) Thread pools lost custom names in opensource refactoring +* [Pull 30](https://github.com/Netflix/Hystrix/pull/30) Simplified access to HystrixCommandMetrics +* Javadoc and README changes + +### Version 1.0.2 ### + +* Javadoc changes + +### Version 1.0.0 ### + +* Initial open source release diff --git a/Hystrix-master/CONTRIBUTING.md b/Hystrix-master/CONTRIBUTING.md new file mode 100644 index 0000000..57f8695 --- /dev/null +++ b/Hystrix-master/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to Hystrix + +If you would like to contribute code you can do so through GitHub by forking the repository and sending a pull request. + +When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. + +## License + +By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/Netflix/Hystrix/blob/master/LICENSE-2.0.txt + +All files are released with the Apache 2.0 license. + +If you are adding a new file it should have a header like this: + +``` +/** + * Copyright 2013 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. + */ + ``` diff --git a/Hystrix-master/LICENSE-2.0.txt b/Hystrix-master/LICENSE-2.0.txt new file mode 100644 index 0000000..7f8ced0 --- /dev/null +++ b/Hystrix-master/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2012 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. diff --git a/Hystrix-master/OSSMETADATA b/Hystrix-master/OSSMETADATA new file mode 100644 index 0000000..8bff0a1 --- /dev/null +++ b/Hystrix-master/OSSMETADATA @@ -0,0 +1 @@ +osslifecycle=maintenance diff --git a/Hystrix-master/README.md b/Hystrix-master/README.md new file mode 100644 index 0000000..c4b4354 --- /dev/null +++ b/Hystrix-master/README.md @@ -0,0 +1,227 @@ + + +# Hystrix: Latency and Fault Tolerance for Distributed Systems + +[![NetflixOSS Lifecycle](https://img.shields.io/osslifecycle/Netflix/hystrix.svg)]() +[![][travis img]][travis] +[![][maven img]][maven] +[![][license img]][license] + +# Hystrix Status +Hystrix is no longer in active development, and is currently in maintenance mode. + +Hystrix (at version 1.5.18) is stable enough to meet the needs of Netflix for our existing applications. Meanwhile, our focus has shifted towards more adaptive implementations that react to an application’s real time performance rather than pre-configured settings (for example, through [adaptive concurrency limits](https://medium.com/@NetflixTechBlog/performance-under-load-3e6fa9a60581)). For the cases where something like Hystrix makes sense, we intend to continue using Hystrix for existing applications, and to leverage open and active projects like [resilience4j](https://github.com/resilience4j/resilience4j) for new internal projects. We are beginning to recommend others do the same. + +Netflix Hystrix is now officially in maintenance mode, with the following expectations to the greater community: +Netflix will no longer actively review issues, merge pull-requests, and release new versions of Hystrix. +We have made a final release of Hystrix (1.5.18) per [issue 1891](https://github.com/Netflix/Hystrix/issues/1891) so that the latest version in Maven Central is aligned with the last known stable version used internally at Netflix (1.5.11). +If members of the community are interested in taking ownership of Hystrix and moving it back into active mode, please reach out to hystrixoss@googlegroups.com. + +Hystrix has served Netflix and the community well over the years, and the transition to maintenance mode is in no way an indication that the concepts and ideas from Hystrix are no longer valuable. On the contrary, Hystrix has inspired many great ideas and projects. We thank everyone at Netflix, and in the greater community, for all the contributions made to Hystrix over the years. + +## Introduction + +Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable. + +## Full Documentation + +See the [Wiki](https://github.com/Netflix/Hystrix/wiki/) for full documentation, examples, operational details and other information. + +See the [Javadoc](http://netflix.github.com/Hystrix/javadoc) for the API. + +## Communication + +- Google Group: [HystrixOSS](http://groups.google.com/d/forum/hystrixoss) +- Twitter: [@HystrixOSS](http://twitter.com/HystrixOSS) +- [GitHub Issues](https://github.com/Netflix/Hystrix/issues) + +## What does it do? + +#### 1) Latency and Fault Tolerance + +Stop cascading failures. Fallbacks and graceful degradation. Fail fast and rapid recovery. + +Thread and semaphore isolation with circuit breakers. + +#### 2) Realtime Operations + +Realtime monitoring and configuration changes. Watch service and property changes take effect immediately as they spread across a fleet. + +Be alerted, make decisions, affect change and see results in seconds. + +#### 3) Concurrency + +Parallel execution. Concurrency aware request caching. Automated batching through request collapsing. + +## Hello World! + +Code to be isolated is wrapped inside the run() method of a HystrixCommand similar to the following: + +```java +public class CommandHelloWorld extends HystrixCommand { + + private final String name; + + public CommandHelloWorld(String name) { + super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); + this.name = name; + } + + @Override + protected String run() { + return "Hello " + name + "!"; + } +} +``` + +This command could be used like this: + +```java +String s = new CommandHelloWorld("Bob").execute(); +Future s = new CommandHelloWorld("Bob").queue(); +Observable s = new CommandHelloWorld("Bob").observe(); +``` + +More examples and information can be found in the [How To Use](https://github.com/Netflix/Hystrix/wiki/How-To-Use) section. + +Example source code can be found in the [hystrix-examples](https://github.com/Netflix/Hystrix/tree/master/hystrix-examples/src/main/java/com/netflix/hystrix/examples) module. + +## Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20a%3A%22hystrix-core%22). + +Change history and version numbers => [CHANGELOG.md](https://github.com/Netflix/Hystrix/blob/master/CHANGELOG.md) + +Example for Maven: + +```xml + + com.netflix.hystrix + hystrix-core + x.y.z + +``` +and for Ivy: + +```xml + +``` + +If you need to download the jars instead of using a build system, create a Maven pom file like this with the desired version: + +```xml + + + 4.0.0 + com.netflix.hystrix.download + hystrix-download + 1.0-SNAPSHOT + Simple POM to download hystrix-core and dependencies + http://github.com/Netflix/Hystrix + + + com.netflix.hystrix + hystrix-core + x.y.z + + + + +``` + +Then execute: + +``` +mvn -f download-hystrix-pom.xml dependency:copy-dependencies +``` + +It will download hystrix-core-*.jar and its dependencies into ./target/dependency/. + +You need Java 6 or later. + +## Build + +To build: + +``` +$ git clone git@github.com:Netflix/Hystrix.git +$ cd Hystrix/ +$ ./gradlew build +``` + +Futher details on building can be found on the [Getting Started](https://github.com/Netflix/Hystrix/wiki/Getting-Started) page of the wiki. + +## Run Demo + +To run a [demo app](https://github.com/Netflix/Hystrix/tree/master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java) do the following: + +``` +$ git clone git@github.com:Netflix/Hystrix.git +$ cd Hystrix/ +./gradlew runDemo +``` + +You will see output similar to the following: + +``` +Request => GetUserAccountCommand[SUCCESS][8ms], GetPaymentInformationCommand[SUCCESS][20ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][101ms], CreditCardCommand[SUCCESS][1075ms] +Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][2ms], GetPaymentInformationCommand[SUCCESS][22ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][130ms], CreditCardCommand[SUCCESS][1050ms] +Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][4ms], GetPaymentInformationCommand[SUCCESS][19ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][145ms], CreditCardCommand[SUCCESS][1301ms] +Request => GetUserAccountCommand[SUCCESS][4ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][93ms], CreditCardCommand[SUCCESS][1409ms] + +##################################################################################### +# CreditCardCommand: Requests: 17 Errors: 0 (0%) Mean: 1171 75th: 1391 90th: 1470 99th: 1486 +# GetOrderCommand: Requests: 21 Errors: 0 (0%) Mean: 100 75th: 144 90th: 207 99th: 230 +# GetUserAccountCommand: Requests: 21 Errors: 4 (19%) Mean: 8 75th: 11 90th: 46 99th: 51 +# GetPaymentInformationCommand: Requests: 21 Errors: 0 (0%) Mean: 18 75th: 21 90th: 24 99th: 25 +##################################################################################### + +Request => GetUserAccountCommand[SUCCESS][10ms], GetPaymentInformationCommand[SUCCESS][16ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][51ms], CreditCardCommand[SUCCESS][922ms] +Request => GetUserAccountCommand[SUCCESS][12ms], GetPaymentInformationCommand[SUCCESS][12ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][68ms], CreditCardCommand[SUCCESS][1257ms] +Request => GetUserAccountCommand[SUCCESS][10ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][78ms], CreditCardCommand[SUCCESS][1295ms] +Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][6ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][153ms], CreditCardCommand[SUCCESS][1321ms] +``` + +This demo simulates 4 different [HystrixCommand](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java) implementations with failures, latency, timeouts and duplicate calls in a multi-threaded environment. + +It logs the results of [HystrixRequestLog](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java) and metrics from [HystrixCommandMetrics](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandMetrics.java). + +## Dashboard + +The hystrix-dashboard component of this project has been deprecated and moved to [Netflix-Skunkworks/hystrix-dashboard](https://github.com/Netflix-Skunkworks/hystrix-dashboard). Please see the README there for more details including important security considerations. + + +## Bugs and Feedback + +For bugs, questions and discussions please use the [GitHub Issues](https://github.com/Netflix/Hystrix/issues). + + +## LICENSE + +Copyright 2013 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 + + + +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. + + +[travis]:https://travis-ci.org/Netflix/Hystrix +[travis img]:https://travis-ci.org/Netflix/Hystrix.svg?branch=master + +[maven]:http://search.maven.org/#search|gav|1|g:"com.netflix.hystrix"%20AND%20a:"hystrix-core" +[maven img]:https://maven-badges.herokuapp.com/maven-central/com.netflix.hystrix/hystrix-core/badge.svg + +[release]:https://github.com/netflix/hystrix/releases +[release img]:https://img.shields.io/github/release/netflix/hystrix.svg + +[license]:LICENSE-2.0.txt +[license img]:https://img.shields.io/badge/License-Apache%202-blue.svg + diff --git a/Hystrix-master/build.gradle b/Hystrix-master/build.gradle new file mode 100644 index 0000000..15e60a9 --- /dev/null +++ b/Hystrix-master/build.gradle @@ -0,0 +1,56 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.netflix.nebula:gradle-extra-configurations-plugin:3.0.3' + } +} + +plugins { + id 'nebula.netflixoss' version '3.4.0' + id 'me.champeau.gradle.jmh' version '0.3.1' + id 'net.saliman.cobertura' version '2.2.8' +} + +ext { + githubProjectName = rootProject.name +} + +allprojects { + repositories { + jcenter() + } + + apply plugin: 'net.saliman.cobertura' +} + +subprojects { + apply plugin: 'nebula.netflixoss' + apply plugin: 'java' + apply plugin: 'nebula.provided-base' + apply plugin: 'nebula.compile-api' + + sourceCompatibility = 1.6 + targetCompatibility = 1.6 + + + + group = "com.netflix.${githubProjectName}" + + eclipse { + classpath { + // include 'provided' dependencies on the classpath + plusConfigurations += [configurations.provided] + downloadSources = true + downloadJavadoc = true + } + } + + idea { + module { + // include 'provided' dependencies on the classpath + scopes.COMPILE.plus += [configurations.provided] + } + } +} diff --git a/Hystrix-master/codequality/checkstyle.xml b/Hystrix-master/codequality/checkstyle.xml new file mode 100644 index 0000000..47c01a2 --- /dev/null +++ b/Hystrix-master/codequality/checkstyle.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Hystrix-master/gradle.properties b/Hystrix-master/gradle.properties new file mode 100644 index 0000000..e69de29 diff --git a/Hystrix-master/gradle/javadocStyleSheet.css b/Hystrix-master/gradle/javadocStyleSheet.css new file mode 100644 index 0000000..fdaada4 --- /dev/null +++ b/Hystrix-master/gradle/javadocStyleSheet.css @@ -0,0 +1,59 @@ +# originally from http://sensemaya.org/files/stylesheet.css and then modified +# http://sensemaya.org/maya/2009/07/10/making-javadoc-more-legible + +/* Javadoc style sheet */ + +/* Define colors, fonts and other style attributes here to override the defaults */ + +/* Page background color */ +body { background-color: #FFFFFF; color:#333; font-size: 100%; } + +body { font-size: 0.875em; line-height: 1.286em; font-family: "Helvetica", "Arial", sans-serif; } + +code { color: #777; line-height: 1.286em; font-family: "Consolas", "Lucida Console", "Droid Sans Mono", "Andale Mono", "Monaco", "Lucida Sans Typewriter"; } + +a { text-decoration: none; color: #16569A; /* also try #2E85ED, #0033FF, #6C93C6, #1D7BBE, #1D8DD2 */ } +a:hover { text-decoration: underline; } + + +table[border="1"] { border: 1px solid #ddd; } +table[border="1"] td, table[border="1"] th { border: 1px solid #ddd; } +table[cellpadding="3"] td { padding: 0.5em; } + +font[size="-1"] { font-size: 0.85em; line-height: 1.5em; } +font[size="-2"] { font-size: 0.8em; } +font[size="+2"] { font-size: 1.4em; line-height: 1.3em; padding: 0.4em 0; } + +/* Headings */ +h1 { font-size: 1.5em; line-height: 1.286em;} +h2.title { color: #c81f08; } + +/* Table colors */ +.TableHeadingColor { background: #ccc; color:#444; } /* Dark mauve */ +.TableSubHeadingColor { background: #ddd; color:#444; } /* Light mauve */ +.TableRowColor { background: #FFFFFF; color:#666; font-size: 0.95em; } /* White */ +.TableRowColor code { color:#000; } /* White */ + +/* Font used in left-hand frame lists */ +.FrameTitleFont { font-size: 100%; } +.FrameHeadingFont { font-size: 90%; } +.FrameItemFont { font-size: 0.9em; line-height: 1.3em; +} +/* Java Interfaces */ +.FrameItemFont a i { + font-style: normal; color: #16569A; +} +.FrameItemFont a:hover i { + text-decoration: underline; +} + + +/* Navigation bar fonts and colors */ +.NavBarCell1 { background-color:#E0E6DF; } /* Light mauve */ +.NavBarCell1Rev { background-color:#16569A; color:#FFFFFF} /* Dark Blue */ +.NavBarFont1 { } +.NavBarFont1Rev { color:#FFFFFF; } + +.NavBarCell2 { background-color:#FFFFFF; color:#000000} +.NavBarCell3 { background-color:#FFFFFF; color:#000000} + diff --git a/Hystrix-master/gradle/wrapper/gradle-wrapper.properties b/Hystrix-master/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d2cc6f3 --- /dev/null +++ b/Hystrix-master/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jun 19 21:27:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/Hystrix-master/gradlew b/Hystrix-master/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/Hystrix-master/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Hystrix-master/gradlew.bat b/Hystrix-master/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/Hystrix-master/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Hystrix-master/hystrix-contrib/README.md b/Hystrix-master/hystrix-contrib/README.md new file mode 100644 index 0000000..edd27fd --- /dev/null +++ b/Hystrix-master/hystrix-contrib/README.md @@ -0,0 +1,15 @@ +## hystrix-contrib + +This is the parent of all "contrib" submodules to Hystrix. + +Examples of what makes sense as a contrib submodule are: + +- alternate implementations of [HystrixMetricsPublisher](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisher.html) +- alternate implementations of [HystrixPropertiesStrategy](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/strategy/properties/HystrixPropertiesStrategy.html) +- request lifecycle implementations (such as [hystrix-request-servlet](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-request-servlet)) +- implementations of [HystrixEventNotifier](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/strategy/eventnotifier/HystrixEventNotifier.html) +- dashboard and monitoring tools + +3rd partly libraries wrapped with Hystrix do not belong here and should be their own project. + +They can however be referenced from the Wiki [Libraries](https://github.com/Netflix/Hystrix/wiki/Libraries) page. diff --git a/Hystrix-master/hystrix-contrib/hystrix-clj/README.md b/Hystrix-master/hystrix-contrib/hystrix-clj/README.md new file mode 100644 index 0000000..e889915 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-clj/README.md @@ -0,0 +1,63 @@ +# Hystrix Clojure Bindings + +This module contains idiomatic Clojure bindings for Hystrix. + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-clj%22). + +Example for Maven: + +```xml + + com.netflix.hystrix + hystrix-clj + x.y.z + +``` + +and for Ivy: + +```xml + +``` + +and for Leiningen: + +```clojure +[com.netflix.hystrix/hystrix-clj "x.y.z"] +``` + +# Usage + +Please see the docstrings in src/com/netflix/hystrix/core.clj for extensive usage info. + +## TL;DR +You have a function that interacts with an untrusted dependency: + +```clojure +(defn make-request + [arg] + ... make the request ...) + +; execute the request +(make-request "baz") +``` + +and you want to make it a Hystrix dependency command. Do this: + +```clojure +(defcommand make-request + [arg] + ... make the request ...) + +; execute the request +(make-request "baz") + +; or queue for async execution +(queue #'make-request "baz") +``` + +# Event Stream + +A Clojure version of hystrix-event-stream can be found at https://github.com/josephwilk/hystrix-event-stream-clj diff --git a/Hystrix-master/hystrix-contrib/hystrix-clj/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-clj/build.gradle new file mode 100644 index 0000000..2d69c73 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-clj/build.gradle @@ -0,0 +1,54 @@ +buildscript { + repositories { + jcenter() + maven{ + name 'clojars' + url 'http://clojars.org/repo' + } + } + dependencies { + classpath 'com.netflix.nebula:nebula-clojure-plugin:4.0.1' + } +} +apply plugin: 'nebula.clojure' // this is a wrapper around clojuresque to make it behave well with other plugins + +repositories { + mavenCentral() + clojarsRepo() +} + +dependencies { + compileApi project(':hystrix-core') + compile 'org.clojure:clojure:1.7.0' +} + +/* + * Add Counterclockwise and include 'provided' dependencies + */ +eclipse { + project { + natures "ccw.nature" + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Define a task that runs an nrepl server. The port is given with the nreplPort +// property: +// gradlew nrepl -PnreplPort=9999 +// or put the property in ~/.gradle/gradle.properties +def nreplPort = 9999 // hardcoding to 9999 until figuring out how to make this not break Eclipse project import when the property isn't defined +configurations { nrepl } +dependencies { nrepl 'org.clojure:tools.nrepl:0.2.1' } +task nrepl(type: JavaExec) { + classpath configurations.nrepl.asPath, + project.sourceSets.main.clojure.srcDirs, + project.sourceSets.test.clojure.srcDirs, + sourceSets.main.runtimeClasspath + main = "clojure.main" + args '--eval', "(ns gradle-nrepl (:require [clojure.tools.nrepl.server :refer (start-server stop-server)]))", + '--eval', "(println \"Starting nrepl server on port $nreplPort\")", + '--eval', "(def server (start-server :port $nreplPort))" +} + +// vim:ft=groovy diff --git a/Hystrix-master/hystrix-contrib/hystrix-clj/src/main/clojure/com/netflix/hystrix/core.clj b/Hystrix-master/hystrix-contrib/hystrix-clj/src/main/clojure/com/netflix/hystrix/core.clj new file mode 100644 index 0000000..3b3ad25 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-clj/src/main/clojure/com/netflix/hystrix/core.clj @@ -0,0 +1,779 @@ +;; +;; 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. + +(ns com.netflix.hystrix.core + "THESE BINDINGS ARE EXPERIMENTAL AND SUBJECT TO CHANGE + + Functions for defining and executing Hystrix dependency commands and collapsers. + + The definition of commands and collapers is separated from their instantiation and execution. + They are represented as plain Clojure maps (see below) which are later instantiated into + functional HystrixCommand or HystrixCollapser instances. defcommand and defcollapser macros + are provided to assist in defining these maps. + + A command or collapser definition map can be passed to the execute and queue functions to + invoke the command. + + The main Hystrix documentation can be found at https://github.com/Netflix/Hystrix. In particular, + you may find the Javadocs useful if you need to drop down into Java: + http://netflix.github.io/Hystrix/javadoc/ + + # HystrixCommand + + A HystrixCommand is a representation for an interaction with a network dependency, or other + untrusted resource. See the Hystrix documentation (https://github.com/Netflix/Hystrix) for + more details. + + A command is defined by a map with the following keys: + + :type + + Always :command. Required. + + :group-key + + A HystrixCommandGroupKey, string, or keyword. Required. + + :command-key + + A HystrixCommandKey, string, or keyword. Required. + + :thread-pool-key + + A HystrixThreadPoolKey, string, or keyword. Optional, defaults to :group-key. + + :run-fn + + The function to run for the command. The function may have any number of + arguments. Required. + + :fallback-fn + + A function with the same args as :run-fn that calculates a fallback result when + the command fails. Optional, defaults to a function that throws UnsupportedOperationException. + + :cache-key-fn + + A function which the same args as :run-fn that calculates a cache key for the + given args. Optional, defaults to nil, i.e. no caching. + + :init-fn + + A function that takes a definition map and HystrixCommand$Setter which should return + a HystrixCommand$Setter (usually the one passed in) to ultimately be passed to the + constructor of the HystrixCommand. For example, + + (fn [_ setter] + (.andCommandPropertiesDefaults setter ...)) + + This is your escape hatch into raw Hystrix. + ... but NOTE: Hystrix does a fair bit of configuration caching and that caching is keyed + by command key. Thus, many of the settings you apply within :init-fn will only apply + the *first time it is invoked*. After that, they're ignored. This means that some REPL-based + dynamicism is lost and that :init-fn shouldn't be used to configure a HystrixCommand at + run-time. Instead use Archaius properties as described in the Hystrix docs. + + The com.netflix.hystrix.core/defcommand macro is a helper for defining this map and storing it + in a var. For example, here's a definition for an addition command: + + ; Define a command with :group-key set to the current namespace, *ns*, and with :command-key + ; set to \"plus\". The function the command executes is clojure.core/+. + (defcommand plus + \"My resilient addition operation\" + [& args] + (apply + args)) + ;=> #'user/plus + + ; Execute the command + (plus 1 2 3 4 5) + ;=> 15 + + ; Queue the command for async operation + (def f (queue #'plus 4 5)) + ;=> java.util.concurrent.Future/clojure.lang.IDeref + + ; Now you can deref the future as usual + @f ; or (.get f) + ;=> 9 + + # HystrixCollapser + + A HystrixCollapser allows multiple HystrixCommand requests to be batched together if the underlying + resource provides such a capability. See the Hystrix documentation (https://github.com/Netflix/Hystrix) + for more details. + + A collapser is defined by a map with the following keys: + + :type + + Always :collapser. Required. + + :collapser-key + + A HystrixCollapserKey, string, or keyword. Required. + + :collapse-fn + + A fn that takes a sequence of arg lists and instantiates a new command to + execute them. Required. See com.netflix.hystrix.core/instantiate. This + function should be completely free of side effects. + + :map-fn + + A fn that takes sequence of arg lists (as passed to :collapse-fn) and the + result from the command created by :collapse-fn. Must return a sequence of + results where the nth element is the result or exception associated with the + nth arg list. The arg lists are in the same order as passed to :collapse-fn. + Required. This function should be completely free of side effects. + + :scope + + The collapser scope, :request or :global. Optional, defaults to :request. + + :shard-fn + + A fn that takes a sequence of arg lists and shards them, returns a sequence of + sequence of arg lists. Optional, defaults to no sharding. This function should + be completely free of side effects. + + :cache-key-fn + + A function that calculates a String cache key for the args passed to the + collapser. Optional, defaults to a function returning nil, i.e. no caching. + This function should be completely free of side effects. + + :init-fn + + A function that takes a definition map and HystrixCollapser$Setter which should return + a HystrixCollapser$Setter (usually the one passed in) to ultimately be passed to the + constructor of the HystrixCollapser. For example, + + (fn [_ setter] + (.andCollapserPropertiesDefaults setter ...)) + + This is your escape hatch into raw Hystrix. Please see additional notes about :init-fn + above. They apply to collapsers as well. + + The com.netflix.hystric.core/defcollapser macro is a helper for defining this map and storing it + in a callable var. + " + (:require [clojure.set :as set]) + (:import [java.util.concurrent Future] + [com.netflix.hystrix + HystrixExecutable + HystrixCommand + HystrixCommand$Setter + HystrixCollapser + HystrixCollapser$Scope + HystrixCollapser$Setter + HystrixCollapser$CollapsedRequest] + [com.netflix.hystrix.strategy.concurrency + HystrixRequestContext])) + +(set! *warn-on-reflection* true) + +(defmacro ^:private key-fn + "Make a function that creates keys of the given class given one of: + + * an instance of class + * a string name + * a keyword name + " + [class] + (let [s (-> class name (str "$Factory/asKey") symbol)] + `(fn [key-name#] + (cond + (nil? key-name#) + nil + (instance? ~class key-name#) + key-name# + (keyword? key-name#) + (~s (name key-name#)) + (string? key-name#) + (~s key-name#) + :else + (throw (IllegalArgumentException. (str "Don't know how to make " ~class " from " key-name#))))))) + +(def command-key + "Given a string or keyword, returns a HystrixCommandKey. nil and HystrixCommandKey instances + are returned unchanged. + + This function is rarely needed since most hystrix-clj functions will do this automatically. + + See: + com.netflix.hystrix.HystrixCommandKey + " + (key-fn com.netflix.hystrix.HystrixCommandKey)) + +(def group-key + "Given a string or keyword, returns a HystrixCommandGroupKey. nil and HystrixCommandGroupKey + instances are returned unchanged. + + This function is rarely needed since most hystrix-clj functions will do this automatically. + + See: + com.netflix.hystrix.HystrixCommandGroupKey + " + (key-fn com.netflix.hystrix.HystrixCommandGroupKey)) + +(def thread-pool-key + "Given a string or keyword, returns a HystrixThreadPoolGroupKey. nil and HystrixThreadPoolKey + instances are returned unchanged. + + This function is rarely needed since most hystrix-clj functions will do this automatically. + + See: + com.netflix.hystrix.HystrixThreadPoolKey + " + (key-fn com.netflix.hystrix.HystrixThreadPoolKey)) + +(def collapser-key + "Given a string or keyword, returns a HystrixCollapserKey. nil and HystrixCollapserKey + instances are returned unchanged. + + This function is rarely needed since most hystrix-clj functions will do this automatically. + + See: + com.netflix.hystrix.HystrixCollapserKey + " + (key-fn com.netflix.hystrix.HystrixCollapserKey)) + +(defn ^HystrixCollapser$Scope collapser-scope + [v] + (cond + (instance? HystrixCollapser$Scope v) + v + (= :request v) + HystrixCollapser$Scope/REQUEST + (= :global v) + HystrixCollapser$Scope/GLOBAL + :else + (throw (IllegalArgumentException. (str "Don't know how to make collapser scope from '" v "'"))))) + +(defn- required-key + [k p msg] + (fn [d] + (when-not (contains? d k) + (throw (IllegalArgumentException. (str k " is required.")))) + (when-not (p (get d k)) + (throw (IllegalArgumentException. (str k " " msg ".")))) + d)) + +(defn- optional-key + [k p msg] + (fn [d] + (if-let [v (get d k)] + (when-not (p v) + (throw (IllegalArgumentException. (str k " " msg "."))))) + d)) +(defn- required-fn [k] (required-key k ifn? "must be a function")) +(defn- optional-fn [k] (optional-key k ifn? "must be a function")) + +;################################################################################ + +(defmulti normalize + "Given a definition map, verify and normalize it, expanding shortcuts to fully-qualified objects, etc. + + Throws IllegalArgumentException if any constraints for the definition map are violated. + " + (fn [definition] (:type definition))) + +(defmulti instantiate* (fn [definition & _] (:type definition))) + +;################################################################################ + +(def ^{:dynamic true :tag HystrixCommand} *command* + "A dynamic var which is bound to the HystrixCommand instance during execution of + :run-fn and :fallback-fn. + + It's occasionally useful, especially for fallbacks, to base the result on the state of + the comand. The fallback might vary based on whether it was triggered by an application + error versus a timeout. + + Note: As always with dynamic vars be careful about scoping. This binding only holds for + the duration of the :run-fn or :fallback-fn. + " + nil) + +;################################################################################ + +(defmacro with-request-context + "Executes body within a new Hystrix Context. + + Initializes a new HystrixRequestContext, executes the code and then shuts down the + context. Evaluates to the result of body. + + Example: + + (with-request-context + (... some code that uses Hystrix ...)) + + See: + com.netflix.hystrix.strategy.concurrency.HystrixRequestContext + " + [& body] + `(let [context# (HystrixRequestContext/initializeContext)] + (try + ~@body + (finally + (.shutdown context#))))) + +(defn command + "Helper function that takes a definition map for a HystrixCommand and returns a normalized + version ready for use with execute and queue. + + See com.netflix.hystrix.core ns documentation for valid keys. + + See: + com.netflix.hystrix.core/defcommand + " + [options-map] + (-> options-map + (assoc :type :command) + normalize)) + +(defn- split-def-meta + "split meta map out of def-style args list" + [opts] + (let [doc? (string? (first opts)) + m (if doc? {:doc (first opts)} {}) + opts (if doc? (rest opts) opts) + + attrs? (map? (first opts)) + m (if attrs? (merge (first opts) m) m) + opts (if attrs? (rest opts) opts)] + [m opts])) + +(defn- extract-hystrix-command-options + [meta-map] + (let [key-map {:hystrix/cache-key-fn :cache-key-fn + :hystrix/fallback-fn :fallback-fn + :hystrix/group-key :group-key + :hystrix/command-key :command-key + :hystrix/thread-pool-key :thread-pool-key + :hystrix/init-fn :init-fn }] + (set/rename-keys (select-keys meta-map (keys key-map)) key-map))) + +(defmacro defcommand + "A macro with the same basic form as clojure.core/defn exception that it wraps the body + of the function in a HystrixCommand. This allows an existing defn to be turned into + a command by simply change \"defn\" to \"defcommand\". + + Additional command options can be provided in the defn attr-map, qualifying their keys with + the :hystrix namespace, e.g. :thread-pool-key becomes :hystrix/thread-pool-key. Obviously, + :hystrix/run-fn is ignored since it's inferred from the body of the macro. + + The *var* defined by this macro can be passed to the execute and queue functions as if + it were a HystrixCommand definition map. The complete definition map is stored under the + :hystrix key in the var's metadata. + + See com.netflix.hystrix.core ns documentation for valid keys. + + Example: + + (defcommand search + \"Fault tolerant search\" + [term] + ... execute service request and return vector of results ...) + + ; Same as above, but add fallback and caching + (defcommand search + \"Fault tolerant search\" + {:hystrix/cache-key-fn identity + :hystrix/fallback-fn (constantly []))} + [term] + ... execute service request and return vector of results ...) + + ; Call it like a normal function + (search \"The Big Lebowski\") + ;=> [... vector of results ...] + + ; Asynchronously execute the search command + (queue #'search \"Fargo\") + ;=> a deref-able future + " + {:arglists '([name doc-string? attr-map? [params*] & body])} + [name & opts] + (let [command-key (str *ns* "/" name ) + group-key (str *ns*) + [m body] (split-def-meta opts) + params (if (vector? (first body)) + (list (first body)) + (map first body)) + m (if-not (contains? m :arglists) + (assoc m :arglists ('quote `(~params))) + m)] + `(let [meta-options# (#'com.netflix.hystrix.core/extract-hystrix-command-options ~m) + run-fn# (fn ~name ~@body) + command-map# (com.netflix.hystrix.core/command (merge {:command-key ~command-key + :group-key ~group-key + :run-fn run-fn# } + meta-options#))] + (def ~(with-meta name m) + (fn [& args#] + (apply com.netflix.hystrix.core/execute command-map# args#))) + (alter-meta! (var ~name) assoc :hystrix command-map#) + (var ~name)))) + +(defn- extract-hystrix-collapser-options + [meta-map] + (let [key-map {:hystrix/collapser-key :collapser-key + :hystrix/shard-fn :shard-fn + :hystrix/scope :scope + :hystrix/cache-key-fn :cache-key-fn + :hystrix/init-fn :init-fn }] + (set/rename-keys (select-keys meta-map (keys key-map)) key-map))) + +(defn collapser + "Helper function that takes a definition map for a HystrixCollapser and returns a normalized + version ready for use with execute and queue. + + See com.netflix.hystrix.core ns documentation for valid keys. + + See: + com.netflix.hystrix.core/defcollapser + " + [{:keys [collapser-key] :as options-map}] + (let [result (-> options-map + (assoc :type :collapser) + normalize)] + result)) + +(defmacro defcollapser + "Define a new collapser bound to the given var with a collapser key created from the current + namespace and the var name. Like clojure.core/defn, takes an optional doc string and metadata + map. The form is similar to defn except that a body for both :map-fn and :collapse-fn must be + provided: + + (defcollapser my-collapser + \"optional doc string\" + {... optional attr map with var metadata ...} + (collapse [arg-lists] ... body of :collapse-fn ...) + (map [arg-lists batch-result] ... body of :map-fn ...)) + + Additional collapser options can be provided in the attr-map, qualifying their keys with + the :hystrix namespace, e.g. :scope becomes :hystrix/scope. Obviously, + :hystrix/collapse-fn and :hystrix/map-fn are ignored since they're inferred from the body + of the macro. + + See com.netflix.hystrix.core ns documentation for valid keys. + + Example: + + (ns my-namespace + :require com.netflix.hystrix.core :refer [defcommand defcollapser instantiate execute queue]) + + ; Suppose there's an existing multi-search command that takes a sequence of multiple search + ; terms and returns a vector of vector of search results with a single server request. + (defcommand multi-search ...) + + ; Now we can define single-term search as a collapser that will collapse multiple + ; in-flight search requests into a single multi-term search request to the server + (defcollapser search + \"Collapsing single-term search command\" + (collapse [arg-lists] + ; Create a multi-search command, passing individual terms as a seq of args + (instantiate multi-search (map first arg-lists))) + (map [arg-lists batch-result] + ; Map from input args to results. Here we assume order is preserve by + ; multi-search so we can return the result list directly + batch-result)) + + ; The search collapser is now defined. It has a collapser key of \"my-namespace/search\". + ; This is used for configuration and metrics. + + ; Syncrhonously execute the search collapser + (search \"The Hudsucker Proxy\") + ;=> [... vector of results ...] + + ; Asynchronously execute the search collapser + (queue search \"Raising Arizona\") + ;=> a deref-able future + " + {:arglists '([name doc-string? attr-map? + (collapse [arg-lists] :collapse-fn body) + (map [arg-lists batch-result] :map-fn body)])} + [name & opts] + (let [full-name (str *ns* "/" name) + [m fns] (split-def-meta opts) + _ (when-not (= 2 (count fns)) + (throw (IllegalArgumentException. "Expected collapse and map forms."))) + getfn (fn [s] (first (filter #(= s (first %)) fns))) + [_ collapse-args & collapse-body] (getfn 'collapse) + [_ map-args & map-body] (getfn 'map)] + `(let [meta-options# (#'com.netflix.hystrix.core/extract-hystrix-collapser-options ~m) + map-fn# (fn ~map-args ~@map-body) + collapse-fn# (fn ~collapse-args ~@collapse-body) + def-map# (com.netflix.hystrix.core/collapser (merge {:collapser-key ~full-name + :map-fn map-fn# + :collapse-fn collapse-fn# } + meta-options#))] + (def ~(with-meta name m) + (fn [& args#] + (apply com.netflix.hystrix.core/execute def-map# args#))) + (alter-meta! (var ~name) assoc :hystrix def-map#) + (var ~name)))) + +(defn ^HystrixExecutable instantiate + "Given a normalized definition map for a command or collapser, 'compiles' it into a HystrixExecutable object + that can be executed, queued, etc. This function should rarely be used. Use execute and queue + instead. + + definition may be any of: + + * A var created with defcommand + * A var created with defcollapser + * A normalized definition map for a command + * A HystrixExecutable instance and no additional arguments + + One typical use case for this function is to create a batch command in the :collapse-fn of a collapser. Another is to get an actual HystrixCommand instance to get access to additional methods + it provides. + + See: + com.neflix.hystrix.core/normalize + com.neflix.hystrix.core/execute + com.neflix.hystrix.core/queue + " + {:arglists '[[defcommand-var & args] + [defcollapser-var & args] + [definition-map & args] + [HystrixExecutable] ]} + [definition & args] + (cond + (var? definition) + (if-let [hm (-> definition meta :hystrix)] + (apply instantiate* hm args) + (throw (IllegalArgumentException. (str "Couldn't find :hystrix metadata on var " definition)))) + + (map? definition) + (apply instantiate* definition args) + + (instance? HystrixExecutable definition) + (if (empty? args) + definition + (throw (IllegalArgumentException. "Trailing args when executing raw HystrixExecutable"))) + + :else + (throw (IllegalArgumentException. (str "Don't know how to make instantiate HystrixExecutable from: " definition))))) + +(defn execute + "Synchronously execute the command or collapser specified by the given normalized definition with + the given arguments. Returns the result of :run-fn. + + NEVER EXECUTE A HystrixExecutable MORE THAN ONCE. + + See: + http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html#execute() + " + [definition & args] + (.execute ^HystrixExecutable (apply instantiate definition args))) + +(defprotocol QueuedCommand + "Protocol implemented by the result of com.netflix.hystrix.core/queue" + (instance [this] "Returns the raw HystrixExecutable instance created by the queued command")) + +(defn- queued-command [^HystrixExecutable instance ^Future future] + (reify + QueuedCommand + (instance [this] instance) + + Future + (get [this] (.get future)) + (get [this timeout timeunit] (.get future timeout timeunit)) + (isCancelled [this] (.isCancelled future)) + (isDone [this] (.isDone future)) + (cancel [this may-interrupt?] (.cancel future may-interrupt?)) + + clojure.lang.IDeref + (deref [this] (.get future)))) + +(defn queue + "Asynchronously queue the command or collapser specified by the given normalized definition with + the given arguments. Returns an object which implements both java.util.concurrent.Future and + clojure.lang.IDeref. + + The returned object also implements the QueuedCommand protocol. + + If definition is already a HystrixExecutable and no args are given, queues it and returns + the same object as described above. NEVER QUEUE A HystrixExecutable MORE THAN ONCE. + + Examples: + + (let [qc (queue my-command 1 2 3)] + ... do something else ... + ; wait for result + @qc) + + See: + http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html#queue() + " + [definition & args] + (let [^HystrixExecutable instance (apply instantiate definition args)] + (queued-command instance (.queue instance)))) + +(defn observe + "Asynchronously execute the command or collapser specified by the given normalized definition + with the given arguments. Returns an rx.Observable which can be subscribed to. + + Note that this will eagerly begin execution of the command, even if there are no subscribers. + Use observe-later for lazy semantics. + + If definition is already a HystrixExecutable and no args are given, observes it and returns + an Observable as described above. NEVER OBSERVE A HystrixExecutable MORE THAN ONCE. + + See: + http://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html#observe() + http://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCollapser.html#observe() + http://netflix.github.io/RxJava/javadoc/rx/Observable.html + " + [definition & args] + (let [^HystrixExecutable instance (apply instantiate definition args)] + (.observe instance))) + +(defprotocol ^:private ObserveLater + "A protocol solely to eliminate reflection warnings because .toObservable + can be found on both HystrixCommand and HystrixCollapser, but not in their + common base class HystrixExecutable." + (^:private observe-later* [this]) + (^:private observe-later-on* [this scheduler])) + +(extend-protocol ObserveLater + HystrixCommand + (observe-later* [this] (.toObservable this)) + (observe-later-on* [this scheduler] (.observeOn (.toObservable this) scheduler)) + HystrixCollapser + (observe-later* [this] (.toObservable this)) + (observe-later-on* [this scheduler] (.observeOn (.toObservable this) scheduler))) + +(defn observe-later + "Same as #'com.netflix.hystrix.core/observe, but command execution does not begin until the + returned Observable is subscribed to. + + See: + http://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html#toObservable()) + http://netflix.github.io/RxJava/javadoc/rx/Observable.html + " + [definition & args] + (observe-later* (apply instantiate definition args))) + +(defn observe-later-on + "Same as #'com.netflix.hystrix.core/observe-later but an explicit scheduler can be provided + for the callback. + + See: + com.netflix.hystrix.core/observe-later + com.netflix.hystrix.core/observe + http://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html#toObservable(Scheduler) + http://netflix.github.io/RxJava/javadoc/rx/Observable.html + " + [definition scheduler & args] + (observe-later-on* (apply instantiate definition args) scheduler)) + +;################################################################################ +; :command impl + +(defmethod normalize :command + [definition] + (-> definition + ((required-fn :run-fn)) + ((optional-fn :fallback-fn)) + ((optional-fn :cache-key-fn)) + ((optional-fn :init-fn)) + + (update-in [:group-key] group-key) + (update-in [:command-key] command-key) + (update-in [:thread-pool-key] thread-pool-key))) + +(defmethod instantiate* :command + [{:keys [group-key command-key thread-pool-key + run-fn fallback-fn cache-key-fn + init-fn] :as def-map} & args] + (let [setter (-> (HystrixCommand$Setter/withGroupKey group-key) + (.andCommandKey command-key) + (.andThreadPoolKey thread-pool-key)) + setter (if init-fn + (init-fn def-map setter) + setter)] + (when (not (instance? HystrixCommand$Setter setter)) + (throw (IllegalStateException. (str ":init-fn didn't return HystrixCommand$Setter instance")))) + (proxy [HystrixCommand] [^HystrixCommand$Setter setter] + (run [] + (binding [*command* this] + (apply run-fn args))) + (getFallback [] + (if fallback-fn + (binding [*command* this] + (apply fallback-fn args)) + (throw (UnsupportedOperationException. "No :fallback-fn provided")))) + (getCacheKey [] (if cache-key-fn + (apply cache-key-fn args)))))) + + +;################################################################################ +; :collapser impl + +(defmethod normalize :collapser + [definition] + (-> definition + ((required-fn :collapse-fn)) + ((required-fn :map-fn)) + ((optional-fn :shard-fn)) + ((optional-fn :cache-key-fn)) + ((optional-fn :init-fn)) + + (update-in [:collapser-key] collapser-key) + (update-in [:scope] (fnil collapser-scope HystrixCollapser$Scope/REQUEST)))) + +(defn- collapsed-request->arg-list + [^HystrixCollapser$CollapsedRequest request] + (.getArgument request)) + +(defmethod instantiate* :collapser + [{:keys [collapser-key scope + collapse-fn map-fn shard-fn cache-key-fn + init-fn] :as def-map} & args] + (let [setter (-> (HystrixCollapser$Setter/withCollapserKey collapser-key) + (.andScope scope)) + setter (if init-fn + (init-fn def-map setter) + setter)] + (when (not (instance? HystrixCollapser$Setter setter)) + (throw (IllegalStateException. (str ":init-fn didn't return HystrixCollapser$Setter instance")))) + (proxy [HystrixCollapser] [^HystrixCollapser$Setter setter] + (getCacheKey [] (if cache-key-fn + (apply cache-key-fn args))) + + (getRequestArgument [] args) + + (createCommand [requests] + (collapse-fn (map collapsed-request->arg-list requests))) + + (shardRequests [requests] + (if shard-fn + [requests] ; TODO implement sharding + [requests])) + + (mapResponseToRequests [batch-response requests] + (let [arg-lists (map collapsed-request->arg-list requests) + mapped-responses (map-fn arg-lists batch-response)] + (if-not (= (count requests) (count mapped-responses)) + (throw (IllegalStateException. + (str ":map-fn of collapser '" collapser-key + "' did not return a result for each request. Expected " (count requests) + ", got " (count mapped-responses))))) + (doseq [[^HystrixCollapser$CollapsedRequest request response] (map vector requests mapped-responses)] + (if (instance? Exception response) + (.setException request ^Exception response) + (.setResponse request response)))))))) diff --git a/Hystrix-master/hystrix-contrib/hystrix-clj/src/test/clojure/com/netflix/hystrix/core_test.clj b/Hystrix-master/hystrix-contrib/hystrix-clj/src/test/clojure/com/netflix/hystrix/core_test.clj new file mode 100644 index 0000000..be73314 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-clj/src/test/clojure/com/netflix/hystrix/core_test.clj @@ -0,0 +1,405 @@ +; +; Copyright 2016 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. +; + +(ns com.netflix.hystrix.core-test + (:use com.netflix.hystrix.core) + (:require [clojure.test :refer [deftest testing is are use-fixtures]]) + (:import [com.netflix.hystrix Hystrix HystrixExecutable] + [com.netflix.hystrix.strategy.concurrency HystrixRequestContext] + [com.netflix.hystrix.exception HystrixRuntimeException])) + +; reset hystrix after each execution, for consistency and sanity +(defn reset-fixture + [f] + (try + (f) + (finally + (Hystrix/reset)))) + +(use-fixtures :once reset-fixture) + +; wrap each test in hystrix context +(defn request-context-fixture + [f] + (try + (HystrixRequestContext/initializeContext) + (f) + (finally + (when-let [c (HystrixRequestContext/getContextForCurrentThread)] + (.shutdown c))))) + +(use-fixtures :each request-context-fixture) + +(deftest test-command-key + (testing "returns nil when input is nil" + (is (nil? (command-key nil)))) + + (testing "returns existing key unchanged" + (let [k (command-key "foo")] + (is (identical? k (command-key k))))) + + (testing "Makes a key from a string" + (let [^com.netflix.hystrix.HystrixCommandKey k (command-key "foo")] + (is (instance? com.netflix.hystrix.HystrixCommandKey k)) + (is (= "foo" (.name k))))) + + (testing "Makes a key from a keyword" + (let [^com.netflix.hystrix.HystrixCommandKey k (command-key :bar)] + (is (instance? com.netflix.hystrix.HystrixCommandKey k)) + (is (= "bar" (.name k)))))) + +(deftest test-normalize-collapser-scope + (testing "throws on nil" + (is (thrown? IllegalArgumentException (collapser-scope nil)))) + (testing "throws on unknown" + (is (thrown? IllegalArgumentException (collapser-scope :foo)))) + (testing "Returns a scope unchanged" + (is (= com.netflix.hystrix.HystrixCollapser$Scope/REQUEST + (collapser-scope com.netflix.hystrix.HystrixCollapser$Scope/REQUEST)))) + (testing "Returns a request scope" + (is (= com.netflix.hystrix.HystrixCollapser$Scope/REQUEST + (collapser-scope :request)))) + (testing "Returns a global scope" + (is (= com.netflix.hystrix.HystrixCollapser$Scope/GLOBAL + (collapser-scope :global))))) + +(deftest test-normalize-command + (testing "throws if :init-fn isn't a fn" + (is (thrown-with-msg? IllegalArgumentException #"^.*init-fn.*$" + (normalize {:type :command + :run-fn + + :init-fn 999}))))) + +(deftest test-normalize-collapser + (testing "throws if :init-fn isn't a fn" + (is (thrown-with-msg? IllegalArgumentException #"^.*init-fn.*$" + (normalize {:type :collapser + :collapse-fn (fn [& args]) + :map-fn (fn [& args]) + :init-fn "foo"}))))) + +(deftest test-instantiate + (let [base-def {:type :command + :group-key :my-group + :command-key :my-command + :run-fn + }] + (testing "makes a HystrixCommand" + (let [c (instantiate (normalize base-def))] + (is (instance? com.netflix.hystrix.HystrixCommand c)))) + + (testing "makes a HystrixCommand and calls :init-fn" + (let [called (atom nil) + init-fn (fn [d s] (reset! called [d s]) s) + c (instantiate (normalize (assoc base-def :init-fn init-fn))) + [d s] @called] + (is (not (nil? @called))) + (is (map? d)) + (is (instance? com.netflix.hystrix.HystrixCommand$Setter s)))) + + (testing "makes a HystrixCommand that executes :run-fn with given args" + (let [c (instantiate (normalize base-def) 99 42)] + (is (= 141 (.execute c))))) + + (testing "makes a HystrixCommand that executes :fallback-fn with given args" + (let [c (instantiate (normalize (assoc base-def + :run-fn (fn [& args] (throw (IllegalStateException.))) + :fallback-fn -)) + 99 42)] + (is (= (- 99 42) (.execute c))))) + + (testing "makes a HystrixCommand that implements getCacheKey" + (with-request-context + (let [call-count (atom 0) ; make sure run-fn is only called once + test-def (normalize (assoc base-def + :run-fn (fn [arg] (swap! call-count inc) (str arg "!")) + :cache-key-fn (fn [arg] arg))) + result1 (.execute (instantiate test-def "hi")) + result2 (.execute (instantiate test-def "hi"))] + (is (= "hi!" result1 result2)) + (is (= 1 @call-count)))))) + (testing "throws if :hystrix metadata isn't found in a var" + (is (thrown? IllegalArgumentException + (instantiate #'map)))) + (testing "throws if it doesn't know what to do" + (is (thrown? IllegalArgumentException + (instantiate [1 2 2]))))) + +(deftest test-execute + (let [base-def {:type :command + :group-key :my-group + :command-key :my-command }] + (testing "executes a HystrixCommand" + (is (= "hello-world") + (execute (instantiate (normalize (assoc base-def :run-fn str)) + "hello" "-" "world")))) + + (testing "throws HystrixRuntimeException if called twice on same instance" + (let [instance (instantiate (normalize (assoc base-def :run-fn str)) "hi")] + (is (thrown? HystrixRuntimeException + (execute instance) + (execute instance))))) + + (testing "throws if there are trailing args" + (is (thrown? IllegalArgumentException + (execute (instantiate (normalize base-def)) "hello" "-" "world")))) + + (testing "instantiates and executes a command" + (is (= "hello-world") + (execute (normalize (assoc base-def :run-fn str)) + "hello" "-" "world"))))) + +(deftest test-queue + (let [base-def {:type :command + :group-key :my-group + :command-key :my-command + :run-fn + }] + + (testing "queues a HystrixCommand" + (is (= "hello-world") + (.get (queue (instantiate (normalize (assoc base-def :run-fn str)) + "hello" "-" "world"))))) + + (testing "throws if there are trailing args" + (is (thrown? IllegalArgumentException + (queue (instantiate (normalize base-def)) "hello" "-" "world")))) + + (testing "instantiates and queues a command" + (let [qc (queue (normalize (assoc base-def :run-fn str)) "hello" "-" "world")] + (is (instance? HystrixExecutable (instance qc))) + (is (future? qc)) + (is (= "hello-world" (.get qc) @qc)) + (is (.isDone qc)))))) + +(defn ^:private wait-for-observable + [^rx.Observable o] + (-> o .toBlocking .single)) + +(deftest test-observe + (let [base-def {:type :command + :group-key :my-group + :command-key :my-command + :run-fn + }] + (testing "observes a HystrixCommand" + (is (= 99 + (-> (instantiate (normalize base-def) 11 88) + observe + wait-for-observable)))) + (testing "throws if there are trailing args" + (is (thrown? IllegalArgumentException + (observe (instantiate (normalize base-def)) 10 23)))) + (testing "instantiates and observes a command" + (let [o (observe (normalize base-def) 75 19 23)] + (is (instance? rx.Observable o)) + (is (= (+ 75 19 23) + (wait-for-observable o))))))) + +(deftest test-observe-later + (let [base-def {:type :command + :group-key :my-group + :command-key :my-command + :run-fn + }] + (testing "observes a HystrixCommand" + (is (= 99 + (-> (instantiate (normalize base-def) 11 88) + observe-later + wait-for-observable)))) + (testing "throws if there are trailing args" + (is (thrown? IllegalArgumentException + (observe-later (instantiate (normalize base-def)) 10 23)))) + (testing "instantiates and observes a command" + (let [o (observe-later (normalize base-def) 75 19 23)] + (is (instance? rx.Observable o)) + (is (= (+ 75 19 23) + (wait-for-observable o))))) + (testing "observes command with a Scheduler" + (let [o (observe-later-on (normalize base-def) + (rx.schedulers.Schedulers/newThread) + 75 19 23)] + (is (instance? rx.Observable o)) + (is (= (+ 75 19 23) + (wait-for-observable o))))))) + +(deftest test-this-command-binding + (let [base-def {:type :command + :group-key :test-this-command-binding-group + :command-key :test-this-command-binding + }] + (testing "this is bound while :run-fn is executing" + (let [captured (atom nil) + command-def (normalize (assoc base-def + :run-fn (fn [] + (reset! captured *command*)))) + command (instantiate command-def)] + (.execute command) + (is (identical? command @captured)))) + + + (testing "this is bound while :fallback-fn is executing" + (let [captured (atom nil) + command-def (normalize (assoc base-def + :run-fn (fn [] (throw (Exception. "FALLBACK!"))) + :fallback-fn (fn [] (reset! captured *command*)) + )) + command (instantiate command-def)] + (.execute command) + (is (identical? command @captured)))))) + +(deftest test-collapser + ; These atoms are only for testing. In real life, collapser functions should *never* + ; have side effects. + (let [batch-calls (atom []) + map-fn-calls (atom []) + collapse-fn-calls (atom []) + to-upper-batch (fn [xs] (mapv #(.toUpperCase ^String %) xs)) + ; Define a batch version of to-upper + command (normalize {:type :command + :group-key :my-group + :command-key :to-upper-batcher + :run-fn (fn [xs] + (swap! batch-calls conj (count xs)) + (to-upper-batch xs))}) + + ; Define a collapser for to-upper + collapser (normalize {:type :collapser + :collapser-key :to-upper-collapser + :collapse-fn (fn [arg-lists] + (swap! collapse-fn-calls conj (count arg-lists)) + ; batch to-upper takes a list of strings, i.e. the first/only + ; arg of each request + (instantiate command (mapv first arg-lists))) + :map-fn (fn [arg-lists uppered] + (swap! map-fn-calls conj (count arg-lists)) + ; batch to-upper returns results in same order as input so + ; we can just to a direct mapping without looking in the + ; results. + uppered) })] + (testing "that it actually works" + (let [n 100 + inputs (mapv #(str "xyx" %) (range n)) + expected-results (to-upper-batch inputs) + to-upper (partial queue collapser) + queued (doall (map (fn [x] + (Thread/sleep 1) + (to-upper x)) + inputs)) + results (doall (map deref queued))] + (println "Got collapses with sizes:" @collapse-fn-calls) + (println "Got maps with sizes:" @map-fn-calls) + (println "Got batches with sizes:" @batch-calls) + (is (= n (count expected-results) (count results))) + (is (= expected-results results)) + (is (not (empty? @collapse-fn-calls))) + (is (not (empty? @map-fn-calls))) + (is (not (empty? @batch-calls))))))) + +(defcommand my-fn-command + "A doc string" + {:meta :data + :hystrix/fallback-fn (constantly 500)} + [a b] + (+ a b)) + +(defcommand my-overload-fn-command + "A doc string" + {:meta :data + :hystrix/fallback-fn (constantly 500)} + ([a b] + (+ a b)) + ([a b c] + (+ a b c))) + +(deftest test-defcommand + (let [hm (-> #'my-fn-command meta :hystrix)] + (testing "defines a fn in a var" + (is (fn? my-fn-command)) + (is (map? hm)) + (is (= "com.netflix.hystrix.core-test/my-fn-command" (.name (:command-key hm)))) + (is (= "com.netflix.hystrix.core-test" (.name (:group-key hm)))) + (is (= :data (-> #'my-fn-command meta :meta))) + (= 500 ((:fallback-fn hm)))) + (testing "defines a functioning command" + (is (= 99 (my-fn-command 88 11))) + (is (= 100 (execute #'my-fn-command 89 11))) + (is (= 101 (deref (queue #'my-fn-command 89 12)))) + (is (= 103 (wait-for-observable (observe #'my-fn-command 90 13)))) + (is (= 105 (wait-for-observable (observe-later #'my-fn-command 91 14)))) + (is (= 107 (wait-for-observable (observe-later-on #'my-fn-command + (rx.schedulers.Schedulers/newThread) + 92 15))))) + (testing "overload functioning command" + (is (= 99 (my-overload-fn-command 88 11))) + (is (= 100 (my-overload-fn-command 88 11 1))) + (is (= 100 (execute #'my-overload-fn-command 89 11))) + (is (= 100 (execute #'my-overload-fn-command 88 11 1))) + (is (= 101 (deref (queue #'my-overload-fn-command 89 12)))) + (is (= 102 (deref (queue #'my-overload-fn-command 89 12 1)))) + (is (= 103 (wait-for-observable (observe #'my-overload-fn-command 90 13)))) + (is (= 104 (wait-for-observable (observe #'my-overload-fn-command 90 13 1)))) + (is (= 105 (wait-for-observable (observe-later #'my-overload-fn-command 91 14)))) + (is (= 106 (wait-for-observable (observe-later #'my-overload-fn-command 91 14 1)))) + (is (= 107 (wait-for-observable (observe-later-on #'my-overload-fn-command + (rx.schedulers.Schedulers/newThread) + 92 15)))) + (is (= 108 (wait-for-observable (observe-later-on #'my-overload-fn-command + (rx.schedulers.Schedulers/newThread) + 92 15 1))))))) + +(defcollapser my-collapser + "a doc string" + {:meta :data} + (collapse [arg-lists] "collapse") + (map [batch-result arg-lists] "map")) + +(deftest test-defcollapser + (let [hm (-> #'my-collapser meta :hystrix)] + (testing "defines a collapser in a var" + (is (fn? my-collapser)) + (is (map? hm)) + (is (= "com.netflix.hystrix.core-test/my-collapser" (.name (:collapser-key hm)))) + (is (= :collapser (:type hm))) + (is (= "a doc string" (-> #'my-collapser meta :doc))) + (is (= :data (-> #'my-collapser meta :meta))) + (is (= "collapse" ((:collapse-fn hm) nil))) + (is (= "map" ((:map-fn hm) nil nil)))))) + +; This is a version of the larger batcher example above using macros +(defcommand to-upper-batcher + "a batch version of to-upper" + [xs] + (mapv #(.toUpperCase ^String %) xs)) + +(defcollapser to-upper-collapser + "A collapser that dispatches to to-upper-batcher" + (collapse [arg-lists] + (instantiate #'to-upper-batcher (mapv first arg-lists))) + (map [arg-lists uppered] + uppered)) + +(deftest test-defd-collapser + (testing "that it actually works" + (let [single-call (to-upper-collapser "v") ; just to make sure collapser works as a fn + n 100 + inputs (mapv #(str "xyx" %) (range n)) + expected-results (to-upper-batcher inputs) + to-upper (partial queue #'to-upper-collapser) + queued (doall (map (fn [x] + (Thread/sleep 1) + (to-upper x)) + inputs)) + results (doall (map deref queued))] + (is (= "V" single-call)) + (is (= n (count expected-results) (count results))) + (is (= expected-results results))))) diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/README.md b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/README.md new file mode 100644 index 0000000..04712cb --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/README.md @@ -0,0 +1,29 @@ +# hystrix-codahale-metrics-publisher + +This is an implementation of [HystrixMetricsPublisher](http://netflix.github.com/Hystrix/javadoc/index.html?com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisher.html) that publishes metrics using [Coda Hale Metrics](http://metrics.codahale.com) version 3. If you are using Yammer Metrics version 2, please use the [hystrix-yammer-metrics-publisher](../hystrix-yammer-metrics-publisher) module instead. + +See the [Metrics & Monitoring](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring) Wiki for more information. + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-codahale-metrics-publisher%22). + +Example for Maven: + +```xml + + com.netflix.hystrix + hystrix-codahale-metrics-publisher + 1.1.2 + +``` + +and for Ivy: + +```xml + +``` + +Example usage (make it work/plug it in): + + HystrixPlugins.getInstance().registerMetricsPublisher(new HystrixCodahaleMetricsPublisher(yourMetricRegistry)); diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/build.gradle new file mode 100644 index 0000000..7dc39e9 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/build.gradle @@ -0,0 +1,6 @@ +dependencies { + compileApi project(':hystrix-core') + compileApi 'io.dropwizard.metrics:metrics-core:3.2.2' + testCompile 'junit:junit-dep:4.10' + testCompile 'org.mockito:mockito-all:1.9.5' +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/ConfigurableCodaHaleMetricFilter.java b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/ConfigurableCodaHaleMetricFilter.java new file mode 100644 index 0000000..b2a2f9e --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/ConfigurableCodaHaleMetricFilter.java @@ -0,0 +1,86 @@ +/** + * Copyright 2013 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.contrib.codahalemetricspublisher; + +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricFilter; +import com.netflix.config.DynamicPropertyFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An implementation of @MetricFilter based upon an Archaius DynamicPropertyFactory + * + * To enable this filter, the property 'filter.graphite.metrics' must be set to TRUE + * + * If this is the case, metrics will be filtered unless METRIC_NAME = true is set in + * the properties + * + * + * eg HystrixCommand.IndiciaService.GetIndicia.countFailure = true + * + * + * For detail on how the metric names are constructed, refer to the source of the + * + * {@link HystrixCodaHaleMetricsPublisherCommand} + * + * and + * + * {@link HystrixCodaHaleMetricsPublisherThreadPool} + * + * classes. + * + * @author Simon Irving + */ +public class ConfigurableCodaHaleMetricFilter implements MetricFilter{ + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurableCodaHaleMetricFilter.class); + + private DynamicPropertyFactory archaiusPropertyFactory; + + + public ConfigurableCodaHaleMetricFilter(DynamicPropertyFactory archaiusPropertyFactory) + { + this.archaiusPropertyFactory = archaiusPropertyFactory; + } + + @Override + public boolean matches(String s, Metric metric) { + + if (!isFilterEnabled()) + { + return true; + } + + boolean matchesFilter = archaiusPropertyFactory.getBooleanProperty(s, false).get(); + + LOGGER.debug("Does metric [{}] match filter? [{}]",s,matchesFilter); + + return matchesFilter; + } + + protected boolean isFilterEnabled() { + + boolean filterEnabled = archaiusPropertyFactory.getBooleanProperty("filter.graphite.metrics", false).get(); + + LOGGER.debug("Is filter enabled? [{}]", filterEnabled); + + return filterEnabled; + } + + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisher.java b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisher.java new file mode 100644 index 0000000..5be3585 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisher.java @@ -0,0 +1,65 @@ +/** + * 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.contrib.codahalemetricspublisher; + +import com.codahale.metrics.MetricRegistry; +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCollapser; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCommand; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherThreadPool; + +/** + * Coda Hale Metrics (https://github.com/codahale/metrics) implementation of {@link HystrixMetricsPublisher}. + */ +public class HystrixCodaHaleMetricsPublisher extends HystrixMetricsPublisher { + private final String metricsRootNode; + private final MetricRegistry metricRegistry; + + public HystrixCodaHaleMetricsPublisher(MetricRegistry metricRegistry) { + this(null, metricRegistry); + } + + public HystrixCodaHaleMetricsPublisher(String metricsRootNode, MetricRegistry metricRegistry) { + this.metricsRootNode = metricsRootNode; + this.metricRegistry = metricRegistry; + } + + @Override + public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + return new HystrixCodaHaleMetricsPublisherCommand(metricsRootNode, commandKey, commandGroupKey, metrics, circuitBreaker, properties, metricRegistry); + } + + @Override + public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + return new HystrixCodaHaleMetricsPublisherThreadPool(metricsRootNode, threadPoolKey, metrics, properties, metricRegistry); + } + + @Override + public HystrixMetricsPublisherCollapser getMetricsPublisherForCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { + return new HystrixCodaHaleMetricsPublisherCollapser(collapserKey, metrics, properties, metricRegistry); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCollapser.java b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCollapser.java new file mode 100644 index 0000000..1602ceb --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCollapser.java @@ -0,0 +1,278 @@ +/** + * 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.contrib.codahalemetricspublisher; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.MetricRegistry; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCollapser; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +/** + * Implementation of {@link HystrixMetricsPublisherCollapser} using Coda Hale Metrics (https://github.com/codahale/metrics) + */ +public class HystrixCodaHaleMetricsPublisherCollapser implements HystrixMetricsPublisherCollapser { + private final HystrixCollapserKey key; + private final HystrixCollapserMetrics metrics; + private final HystrixCollapserProperties properties; + private final MetricRegistry metricRegistry; + private final String metricType; + + static final Logger logger = LoggerFactory.getLogger(HystrixCodaHaleMetricsPublisherCollapser.class); + + public HystrixCodaHaleMetricsPublisherCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties, MetricRegistry metricRegistry) { + this.key = collapserKey; + this.metrics = metrics; + this.properties = properties; + this.metricRegistry = metricRegistry; + this.metricType = key.name(); + } + + /** + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-codahale-metrics-publisher, + * the code below may reference a HystrixRollingNumberEvent that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + */ + @Override + public void initialize() { + // allow monitor to know exactly at what point in time these stats are for so they can be plotted accurately + metricRegistry.register(createMetricName("currentTime"), new Gauge() { + @Override + public Long getValue() { + return System.currentTimeMillis(); + } + }); + + // cumulative counts + safelyCreateCumulativeCountForEvent("countRequestsBatched", new Func0() { + + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSER_REQUEST_BATCHED; + } + }); + safelyCreateCumulativeCountForEvent("countBatches", new Func0() { + + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSER_BATCH; + } + }); + safelyCreateCumulativeCountForEvent("countResponsesFromCache", new Func0() { + + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.RESPONSE_FROM_CACHE; + } + }); + + // rolling counts + safelyCreateRollingCountForEvent("rollingRequestsBatched", new Func0() { + + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSER_REQUEST_BATCHED; + } + }); + safelyCreateRollingCountForEvent("rollingBatches", new Func0() { + + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSER_BATCH; + } + }); + safelyCreateRollingCountForEvent("rollingCountResponsesFromCache", new Func0() { + + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.RESPONSE_FROM_CACHE; + } + }); + + // batch size metrics + metricRegistry.register(createMetricName("batchSize_mean"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getBatchSizeMean(); + } + }); + metricRegistry.register(createMetricName("batchSize_percentile_25"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getBatchSizePercentile(25); + } + }); + metricRegistry.register(createMetricName("batchSize_percentile_50"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getBatchSizePercentile(50); + } + }); + metricRegistry.register(createMetricName("batchSize_percentile_75"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getBatchSizePercentile(75); + } + }); + metricRegistry.register(createMetricName("batchSize_percentile_90"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getBatchSizePercentile(90); + } + }); + metricRegistry.register(createMetricName("batchSize_percentile_99"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getBatchSizePercentile(99); + } + }); + metricRegistry.register(createMetricName("batchSize_percentile_995"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getBatchSizePercentile(99.5); + } + }); + + // shard size metrics + metricRegistry.register(createMetricName("shardSize_mean"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getShardSizeMean(); + } + }); + metricRegistry.register(createMetricName("shardSize_percentile_25"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getShardSizePercentile(25); + } + }); + metricRegistry.register(createMetricName("shardSize_percentile_50"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getShardSizePercentile(50); + } + }); + metricRegistry.register(createMetricName("shardSize_percentile_75"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getShardSizePercentile(75); + } + }); + metricRegistry.register(createMetricName("shardSize_percentile_90"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getShardSizePercentile(90); + } + }); + metricRegistry.register(createMetricName("shardSize_percentile_99"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getShardSizePercentile(99); + } + }); + metricRegistry.register(createMetricName("shardSize_percentile_995"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getShardSizePercentile(99.5); + } + }); + + // properties (so the values can be inspected and monitored) + metricRegistry.register(createMetricName("propertyValue_rollingStatisticalWindowInMilliseconds"), new Gauge() { + @Override + public Number getValue() { + return properties.metricsRollingStatisticalWindowInMilliseconds().get(); + } + }); + + metricRegistry.register(createMetricName("propertyValue_requestCacheEnabled"), new Gauge() { + @Override + public Boolean getValue() { + return properties.requestCacheEnabled().get(); + } + }); + + metricRegistry.register(createMetricName("propertyValue_maxRequestsInBatch"), new Gauge() { + @Override + public Number getValue() { + return properties.maxRequestsInBatch().get(); + } + }); + + metricRegistry.register(createMetricName("propertyValue_timerDelayInMilliseconds"), new Gauge() { + @Override + public Number getValue() { + return properties.timerDelayInMilliseconds().get(); + } + }); + } + + protected String createMetricName(String name) { + return MetricRegistry.name("", metricType, name); + } + + protected void createCumulativeCountForEvent(final String name, final HystrixRollingNumberEvent event) { + metricRegistry.register(createMetricName(name), new Gauge() { + @Override + public Long getValue() { + return metrics.getCumulativeCount(event); + } + }); + } + + protected void safelyCreateCumulativeCountForEvent(final String name, final Func0 eventThunk) { + metricRegistry.register(createMetricName(name), new Gauge() { + @Override + public Long getValue() { + try { + return metrics.getCumulativeCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing CodaHale metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }); + } + + protected void createRollingCountForEvent(final String name, final HystrixRollingNumberEvent event) { + metricRegistry.register(createMetricName(name), new Gauge() { + @Override + public Long getValue() { + return metrics.getRollingCount(event); + } + }); + } + + protected void safelyCreateRollingCountForEvent(final String name, final Func0 eventThunk) { + metricRegistry.register(createMetricName(name), new Gauge() { + @Override + public Long getValue() { + try { + return metrics.getRollingCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing CodaHale metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCommand.java b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCommand.java new file mode 100644 index 0000000..79fc9a2 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCommand.java @@ -0,0 +1,559 @@ +/** + * 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.contrib.codahalemetricspublisher; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.MetricRegistry; +import com.netflix.hystrix.*; +import com.netflix.hystrix.metric.consumer.*; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCommand; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +/** + * Implementation of {@link HystrixMetricsPublisherCommand} using Coda Hale Metrics (https://github.com/codahale/metrics) + */ +public class HystrixCodaHaleMetricsPublisherCommand implements HystrixMetricsPublisherCommand { + private final String metricsRootNode; + private final HystrixCommandKey key; + private final HystrixCommandGroupKey commandGroupKey; + private final HystrixCommandMetrics metrics; + private final HystrixCircuitBreaker circuitBreaker; + private final HystrixCommandProperties properties; + private final MetricRegistry metricRegistry; + private final String metricGroup; + private final String metricType; + + static final Logger logger = LoggerFactory.getLogger(HystrixCodaHaleMetricsPublisherCommand.class); + + public HystrixCodaHaleMetricsPublisherCommand(String metricsRootNode, HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties, MetricRegistry metricRegistry) { + this.metricsRootNode = metricsRootNode; + this.key = commandKey; + this.commandGroupKey = commandGroupKey; + this.metrics = metrics; + this.circuitBreaker = circuitBreaker; + this.properties = properties; + this.metricRegistry = metricRegistry; + this.metricGroup = commandGroupKey.name(); + this.metricType = key.name(); + } + + /** + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-codahale-metrics-publisher, + * the code below may reference a HystrixRollingNumberEvent that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + */ + @Override + public void initialize() { + metricRegistry.register(createMetricName("isCircuitBreakerOpen"), new Gauge() { + @Override + public Boolean getValue() { + return circuitBreaker.isOpen(); + } + }); + + // allow monitor to know exactly at what point in time these stats are for so they can be plotted accurately + metricRegistry.register(createMetricName("currentTime"), new Gauge() { + @Override + public Long getValue() { + return System.currentTimeMillis(); + } + }); + + // cumulative counts + safelyCreateCumulativeCountForEvent("countBadRequests", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.BAD_REQUEST; + } + }); + safelyCreateCumulativeCountForEvent("countCollapsedRequests", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSED; + } + }); + safelyCreateCumulativeCountForEvent("countEmit", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.EMIT; + } + }); + safelyCreateCumulativeCountForEvent("countExceptionsThrown", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.EXCEPTION_THROWN; + } + }); + safelyCreateCumulativeCountForEvent("countFailure", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FAILURE; + } + }); + safelyCreateCumulativeCountForEvent("countFallbackEmit", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_EMIT; + } + }); + safelyCreateCumulativeCountForEvent("countFallbackFailure", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_FAILURE; + } + }); + safelyCreateCumulativeCountForEvent("countFallbackDisabled", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_DISABLED; + } + }); + safelyCreateCumulativeCountForEvent("countFallbackMissing", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_MISSING; + } + }); + safelyCreateCumulativeCountForEvent("countFallbackRejection", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_REJECTION; + } + }); + safelyCreateCumulativeCountForEvent("countFallbackSuccess", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_SUCCESS; + } + }); + safelyCreateCumulativeCountForEvent("countResponsesFromCache", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.RESPONSE_FROM_CACHE; + } + }); + safelyCreateCumulativeCountForEvent("countSemaphoreRejected", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.SEMAPHORE_REJECTED; + } + }); + safelyCreateCumulativeCountForEvent("countShortCircuited", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.SHORT_CIRCUITED; + } + }); + safelyCreateCumulativeCountForEvent("countSuccess", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.SUCCESS; + } + }); + safelyCreateCumulativeCountForEvent("countThreadPoolRejected", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.THREAD_POOL_REJECTED; + } + }); + safelyCreateCumulativeCountForEvent("countTimeout", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.TIMEOUT; + } + }); + + // rolling counts + safelyCreateRollingCountForEvent("rollingCountBadRequests", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.BAD_REQUEST; + } + }); + safelyCreateRollingCountForEvent("rollingCountCollapsedRequests", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSED; + } + }); + safelyCreateRollingCountForEvent("rollingCountEmit", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.EMIT; + } + }); + safelyCreateRollingCountForEvent("rollingCountExceptionsThrown", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.EXCEPTION_THROWN; + } + }); + safelyCreateRollingCountForEvent("rollingCountFailure", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FAILURE; + } + }); + safelyCreateRollingCountForEvent("rollingCountFallbackEmit", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_EMIT; + } + }); + safelyCreateRollingCountForEvent("rollingCountFallbackFailure", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_FAILURE; + } + }); + safelyCreateRollingCountForEvent("rollingCountFallbackDisabled", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_DISABLED; + } + }); + safelyCreateRollingCountForEvent("rollingCountFallbackMissing", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_MISSING; + } + }); + safelyCreateRollingCountForEvent("rollingCountFallbackRejection", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_REJECTION; + } + }); + safelyCreateRollingCountForEvent("rollingCountFallbackSuccess", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.FALLBACK_SUCCESS; + } + }); + safelyCreateRollingCountForEvent("rollingCountResponsesFromCache", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.RESPONSE_FROM_CACHE; + } + }); + safelyCreateRollingCountForEvent("rollingCountSemaphoreRejected", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.SEMAPHORE_REJECTED; + } + }); + safelyCreateRollingCountForEvent("rollingCountShortCircuited", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.SHORT_CIRCUITED; + } + }); + safelyCreateRollingCountForEvent("rollingCountSuccess", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.SUCCESS; + } + }); + safelyCreateRollingCountForEvent("rollingCountThreadPoolRejected", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.THREAD_POOL_REJECTED; + } + }); + safelyCreateRollingCountForEvent("rollingCountTimeout", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.TIMEOUT; + } + }); + // the rolling number of MaxConcurrentExecutionCount. Can be used to determine saturation + safelyCreateRollingCountForEvent("rollingMaxConcurrentExecutionCount", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE; + } + }); + + // the number of executionSemaphorePermits in use right now + metricRegistry.register(createMetricName("executionSemaphorePermitsInUse"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getCurrentConcurrentExecutionCount(); + } + }); + + // error percentage derived from current metrics + metricRegistry.register(createMetricName("errorPercentage"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getHealthCounts().getErrorPercentage(); + } + }); + + // latency metrics + metricRegistry.register(createMetricName("latencyExecute_mean"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getExecutionTimeMean(); + } + }); + metricRegistry.register(createMetricName("latencyExecute_percentile_5"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getExecutionTimePercentile(5); + } + }); + metricRegistry.register(createMetricName("latencyExecute_percentile_25"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getExecutionTimePercentile(25); + } + }); + metricRegistry.register(createMetricName("latencyExecute_percentile_50"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getExecutionTimePercentile(50); + } + }); + metricRegistry.register(createMetricName("latencyExecute_percentile_75"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getExecutionTimePercentile(75); + } + }); + metricRegistry.register(createMetricName("latencyExecute_percentile_90"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getExecutionTimePercentile(90); + } + }); + metricRegistry.register(createMetricName("latencyExecute_percentile_99"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getExecutionTimePercentile(99); + } + }); + metricRegistry.register(createMetricName("latencyExecute_percentile_995"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getExecutionTimePercentile(99.5); + } + }); + + metricRegistry.register(createMetricName("latencyTotal_mean"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getTotalTimeMean(); + } + }); + metricRegistry.register(createMetricName("latencyTotal_percentile_5"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getTotalTimePercentile(5); + } + }); + metricRegistry.register(createMetricName("latencyTotal_percentile_25"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getTotalTimePercentile(25); + } + }); + metricRegistry.register(createMetricName("latencyTotal_percentile_50"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getTotalTimePercentile(50); + } + }); + metricRegistry.register(createMetricName("latencyTotal_percentile_75"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getTotalTimePercentile(75); + } + }); + metricRegistry.register(createMetricName("latencyTotal_percentile_90"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getTotalTimePercentile(90); + } + }); + metricRegistry.register(createMetricName("latencyTotal_percentile_99"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getTotalTimePercentile(99); + } + }); + metricRegistry.register(createMetricName("latencyTotal_percentile_995"), new Gauge() { + @Override + public Integer getValue() { + return metrics.getTotalTimePercentile(99.5); + } + }); + + // group + metricRegistry.register(createMetricName("commandGroup"), new Gauge() { + @Override + public String getValue() { + return commandGroupKey != null ? commandGroupKey.name() : null; + } + }); + + // properties (so the values can be inspected and monitored) + metricRegistry.register(createMetricName("propertyValue_rollingStatisticalWindowInMilliseconds"), new Gauge() { + @Override + public Number getValue() { + return properties.metricsRollingStatisticalWindowInMilliseconds().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_circuitBreakerRequestVolumeThreshold"), new Gauge() { + @Override + public Number getValue() { + return properties.circuitBreakerRequestVolumeThreshold().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_circuitBreakerSleepWindowInMilliseconds"), new Gauge() { + @Override + public Number getValue() { + return properties.circuitBreakerSleepWindowInMilliseconds().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_circuitBreakerErrorThresholdPercentage"), new Gauge() { + @Override + public Number getValue() { + return properties.circuitBreakerErrorThresholdPercentage().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_circuitBreakerForceOpen"), new Gauge() { + @Override + public Boolean getValue() { + return properties.circuitBreakerForceOpen().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_circuitBreakerForceClosed"), new Gauge() { + @Override + public Boolean getValue() { + return properties.circuitBreakerForceClosed().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_executionIsolationThreadTimeoutInMilliseconds"), new Gauge() { + @Override + public Number getValue() { + return properties.executionTimeoutInMilliseconds().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_executionTimeoutInMilliseconds"), new Gauge() { + @Override + public Number getValue() { + return properties.executionTimeoutInMilliseconds().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_executionIsolationStrategy"), new Gauge() { + @Override + public String getValue() { + return properties.executionIsolationStrategy().get().name(); + } + }); + metricRegistry.register(createMetricName("propertyValue_metricsRollingPercentileEnabled"), new Gauge() { + @Override + public Boolean getValue() { + return properties.metricsRollingPercentileEnabled().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_requestCacheEnabled"), new Gauge() { + @Override + public Boolean getValue() { + return properties.requestCacheEnabled().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_requestLogEnabled"), new Gauge() { + @Override + public Boolean getValue() { + return properties.requestLogEnabled().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_executionIsolationSemaphoreMaxConcurrentRequests"), new Gauge() { + @Override + public Number getValue() { + return properties.executionIsolationSemaphoreMaxConcurrentRequests().get(); + } + }); + metricRegistry.register(createMetricName("propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests"), new Gauge() { + @Override + public Number getValue() { + return properties.fallbackIsolationSemaphoreMaxConcurrentRequests().get(); + } + }); + + RollingCommandEventCounterStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + CumulativeCommandEventCounterStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + RollingCommandLatencyDistributionStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + RollingCommandUserLatencyDistributionStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + RollingCommandMaxConcurrencyStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + } + + protected String createMetricName(String name) { + return MetricRegistry.name(metricsRootNode, metricGroup, metricType, name); + } + + protected void createCumulativeCountForEvent(final String name, final HystrixRollingNumberEvent event) { + metricRegistry.register(createMetricName(name), new Gauge() { + @Override + public Long getValue() { + return metrics.getCumulativeCount(event); + } + }); + } + + protected void safelyCreateCumulativeCountForEvent(final String name, final Func0 eventThunk) { + metricRegistry.register(createMetricName(name), new Gauge() { + @Override + public Long getValue() { + try { + return metrics.getCumulativeCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing CodaHale metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }); + } + + protected void createRollingCountForEvent(final String name, final HystrixRollingNumberEvent event) { + metricRegistry.register(createMetricName(name), new Gauge() { + @Override + public Long getValue() { + return metrics.getRollingCount(event); + } + }); + } + + protected void safelyCreateRollingCountForEvent(final String name, final Func0 eventThunk) { + metricRegistry.register(createMetricName(name), new Gauge() { + @Override + public Long getValue() { + try { + return metrics.getRollingCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing CodaHale metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherThreadPool.java b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherThreadPool.java new file mode 100644 index 0000000..f403276 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherThreadPool.java @@ -0,0 +1,190 @@ +/** + * 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.contrib.codahalemetricspublisher; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.MetricRegistry; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherThreadPool; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link HystrixMetricsPublisherThreadPool} using Coda Hale Metrics (https://github.com/codahale/metrics) + */ +public class HystrixCodaHaleMetricsPublisherThreadPool implements HystrixMetricsPublisherThreadPool { + private final String metricsRootNode; + private final HystrixThreadPoolKey key; + private final HystrixThreadPoolMetrics metrics; + private final HystrixThreadPoolProperties properties; + private final MetricRegistry metricRegistry; + private final String metricGroup; + private final String metricType; + + static final Logger logger = LoggerFactory.getLogger(HystrixCodaHaleMetricsPublisherThreadPool.class); + + public HystrixCodaHaleMetricsPublisherThreadPool(String metricsRootNode, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties, MetricRegistry metricRegistry) { + this.metricsRootNode = metricsRootNode; + this.key = threadPoolKey; + this.metrics = metrics; + this.properties = properties; + this.metricRegistry = metricRegistry; + this.metricGroup = "HystrixThreadPool"; + this.metricType = key.name(); + } + + /** + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-codahale-metrics-publisher, + * the code below may reference a HystrixRollingNumberEvent that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + */ + @Override + public void initialize() { + metricRegistry.register(createMetricName("name"), new Gauge() { + @Override + public String getValue() { + return key.name(); + } + }); + + // allow monitor to know exactly at what point in time these stats are for so they can be plotted accurately + metricRegistry.register(createMetricName("currentTime"), new Gauge() { + @Override + public Long getValue() { + return System.currentTimeMillis(); + } + }); + + metricRegistry.register(createMetricName("threadActiveCount"), new Gauge() { + @Override + public Number getValue() { + return metrics.getCurrentActiveCount(); + } + }); + + metricRegistry.register(createMetricName("completedTaskCount"), new Gauge() { + @Override + public Number getValue() { + return metrics.getCurrentCompletedTaskCount(); + } + }); + + metricRegistry.register(createMetricName("largestPoolSize"), new Gauge() { + @Override + public Number getValue() { + return metrics.getCurrentLargestPoolSize(); + } + }); + + metricRegistry.register(createMetricName("totalTaskCount"), new Gauge() { + @Override + public Number getValue() { + return metrics.getCurrentTaskCount(); + } + }); + + metricRegistry.register(createMetricName("queueSize"), new Gauge() { + @Override + public Number getValue() { + return metrics.getCurrentQueueSize(); + } + }); + + metricRegistry.register(createMetricName("rollingMaxActiveThreads"), new Gauge() { + @Override + public Number getValue() { + return metrics.getRollingMaxActiveThreads(); + } + }); + + metricRegistry.register(createMetricName("countThreadsExecuted"), new Gauge() { + @Override + public Number getValue() { + return metrics.getCumulativeCountThreadsExecuted(); + } + }); + + metricRegistry.register(createMetricName("rollingCountCommandsRejected"), new Gauge() { + @Override + public Number getValue() { + try { + return metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED); + } catch (NoSuchFieldError error) { + logger.error("While publishing CodaHale metrics, error looking up eventType for : rollingCountCommandsRejected. Please check that all Hystrix versions are the same!"); + return 0L; + } + } + }); + + metricRegistry.register(createMetricName("rollingCountThreadsExecuted"), new Gauge() { + @Override + public Number getValue() { + return metrics.getRollingCountThreadsExecuted(); + } + }); + + // properties + metricRegistry.register(createMetricName("propertyValue_corePoolSize"), new Gauge() { + @Override + public Number getValue() { + return properties.coreSize().get(); + } + }); + + metricRegistry.register(createMetricName("propertyValue_maximumSize"), new Gauge() { + @Override + public Number getValue() { + return properties.maximumSize().get(); + } + }); + + metricRegistry.register(createMetricName("propertyValue_actualMaximumSize"), new Gauge() { + @Override + public Number getValue() { + return properties.actualMaximumSize(); + } + }); + + metricRegistry.register(createMetricName("propertyValue_keepAliveTimeInMinutes"), new Gauge() { + @Override + public Number getValue() { + return properties.keepAliveTimeMinutes().get(); + } + }); + + metricRegistry.register(createMetricName("propertyValue_queueSizeRejectionThreshold"), new Gauge() { + @Override + public Number getValue() { + return properties.queueSizeRejectionThreshold().get(); + } + }); + + metricRegistry.register(createMetricName("propertyValue_maxQueueSize"), new Gauge() { + @Override + public Number getValue() { + return properties.maxQueueSize().get(); + } + }); + } + + protected String createMetricName(String name) { + return MetricRegistry.name(metricsRootNode, metricGroup, metricType, name); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/codahalemetricspublisher/ConfigurableCodaHaleMetricFilterTest.java b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/codahalemetricspublisher/ConfigurableCodaHaleMetricFilterTest.java new file mode 100644 index 0000000..565155a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/codahalemetricspublisher/ConfigurableCodaHaleMetricFilterTest.java @@ -0,0 +1,94 @@ +/** + * Copyright 2013 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.contrib.codahalemetricspublisher; + +import com.codahale.metrics.Metric; +import com.netflix.config.DynamicBooleanProperty; +import com.netflix.config.DynamicPropertyFactory; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +/** + * Test the ConfigurableCodaHaleMetricFilter + * + * @author Simon Irving + */ +public class ConfigurableCodaHaleMetricFilterTest { + + private Metric metric = mock(Metric.class); + + private final static DynamicPropertyFactory archiausPropertyFactory = mock(DynamicPropertyFactory.class); + + private static final DynamicBooleanProperty DYNAMIC_BOOLEAN_TRUE = mock(DynamicBooleanProperty.class); + private static final DynamicBooleanProperty DYNAMIC_BOOLEAN_FALSE = mock(DynamicBooleanProperty.class); + + @BeforeClass + public static void initialiseMocks() + { + when(archiausPropertyFactory.getBooleanProperty(any(String.class), any(Boolean.class))).thenReturn(DYNAMIC_BOOLEAN_FALSE); + when(archiausPropertyFactory.getBooleanProperty(eq("this.metric.is.allowed"), any(Boolean.class))).thenReturn(DYNAMIC_BOOLEAN_TRUE); + when(DYNAMIC_BOOLEAN_TRUE.get()).thenReturn(true); + when(DYNAMIC_BOOLEAN_FALSE.get()).thenReturn(false); + } + + @After + public void assertMetricsNotTouched() + { + verifyZeroInteractions(metric); + } + + @Test + public void testMetricConfiguredInFilterWithFilterEnabled() + { + when(archiausPropertyFactory.getBooleanProperty(eq("filter.graphite.metrics"), any(Boolean.class))).thenReturn(DYNAMIC_BOOLEAN_TRUE); + ConfigurableCodaHaleMetricFilter filter = new ConfigurableCodaHaleMetricFilter(archiausPropertyFactory); + assertTrue(filter.matches("this.metric.is.allowed", metric)); + } + + @Test + public void testMetricConfiguredInFilterWithFilterDisabled() + { + when(archiausPropertyFactory.getBooleanProperty(eq("filter.graphite.metrics"), any(Boolean.class))).thenReturn(DYNAMIC_BOOLEAN_FALSE); + ConfigurableCodaHaleMetricFilter filter = new ConfigurableCodaHaleMetricFilter(archiausPropertyFactory); + assertTrue(filter.matches("this.metric.is.allowed", metric)); + } + + @Test + public void testMetricNotConfiguredInFilterWithFilterEnabled() + { + when(archiausPropertyFactory.getBooleanProperty(eq("filter.graphite.metrics"), any(Boolean.class))).thenReturn(DYNAMIC_BOOLEAN_TRUE); + ConfigurableCodaHaleMetricFilter filter = new ConfigurableCodaHaleMetricFilter(archiausPropertyFactory); + assertFalse(filter.matches("this.metric.is.not.allowed", metric)); + } + + @Test + public void testMetricNotConfiguredInFilterWithFilterDisabled() + { + when(archiausPropertyFactory.getBooleanProperty(eq("filter.graphite.metrics"), any(Boolean.class))).thenReturn(DYNAMIC_BOOLEAN_FALSE); + ConfigurableCodaHaleMetricFilter filter = new ConfigurableCodaHaleMetricFilter(archiausPropertyFactory); + assertTrue(filter.matches("this.metric.is.not.allowed", metric)); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCommandTest.java b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCommandTest.java new file mode 100644 index 0000000..d98015a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-codahale-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/codahalemetricspublisher/HystrixCodaHaleMetricsPublisherCommandTest.java @@ -0,0 +1,49 @@ +package com.netflix.hystrix.contrib.codahalemetricspublisher; + +import com.codahale.metrics.MetricRegistry; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.strategy.HystrixPlugins; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class HystrixCodaHaleMetricsPublisherCommandTest { + private final MetricRegistry metricRegistry = new MetricRegistry(); + + @Before + public void setup() { + HystrixPlugins.getInstance().registerMetricsPublisher(new HystrixCodaHaleMetricsPublisher("hystrix", metricRegistry)); + } + + @Test + public void testCommandSuccess() throws InterruptedException { + Command command = new Command(); + command.execute(); + + Thread.sleep(1000); + + assertThat((Long) metricRegistry.getGauges().get("hystrix.testGroup.testCommand.countSuccess").getValue(), is(1L)); + assertThat((Long) metricRegistry.getGauges().get("hystrix.HystrixThreadPool.threadGroup.totalTaskCount").getValue(), is(1L)); + + } + + private static class Command extends HystrixCommand { + final static HystrixCommandKey hystrixCommandKey = HystrixCommandKey.Factory.asKey("testCommand"); + final static HystrixCommandGroupKey hystrixCommandGroupKey = HystrixCommandGroupKey.Factory.asKey("testGroup"); + final static HystrixThreadPoolKey hystrixThreadPool = HystrixThreadPoolKey.Factory.asKey("threadGroup"); + + Command() { + super(Setter.withGroupKey(hystrixCommandGroupKey).andCommandKey(hystrixCommandKey).andThreadPoolKey(hystrixThreadPool)); + } + + @Override + protected Void run() throws Exception { + return null; + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/README.md b/Hystrix-master/hystrix-contrib/hystrix-javanica/README.md new file mode 100644 index 0000000..3a8b738 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/README.md @@ -0,0 +1,821 @@ +# hystrix-javanica + +Java language has a great advantages over other languages such as reflection and annotations. +All modern frameworks such as Spring, Hibernate, myBatis and etc. seek to use this advantages to the maximum. +The idea of introduction annotations in Hystrix is obvious solution for improvement. Currently using Hystrix involves writing a lot of code that is a barrier to rapid development. You likely be spending a lot of time on writing a Hystrix commands. Idea of the Javanica project is make easier using of Hystrix by the introduction of support annotations. + +First of all in order to use hystrix-javanica you need to add hystrix-javanica dependency in your project. + +Example for Maven: +```xml + + com.netflix.hystrix + hystrix-javanica + x.y.z + +``` + +To implement AOP functionality in the project was used AspectJ library. If in your project already used AspectJ then you need to add hystrix aspect in aop.xml as below: +```xml + + ... + + ... + +``` +More about AspectJ configuration read [here] (http://www.eclipse.org/aspectj/doc/next/devguide/ltw-configuration.html) + + +If you use Spring AOP in your project then you need to add specific configuration using Spring AOP namespace in order to make Spring capable to manage aspects which were written using AspectJ and declare `HystrixCommandAspect` as Spring bean like below: + +```xml + + +``` + +Or if you are using Spring code configuration: + +```java +@Configuration +public class HystrixConfiguration { + + @Bean + public HystrixCommandAspect hystrixAspect() { + return new HystrixCommandAspect(); + } + +} +``` + +It doesn't matter which approach you use to create proxies in Spring, javanica works fine with JDK and CGLIB proxies. If you use another framework for aop which supports AspectJ and uses other libs (Javassist for instance) to create proxies then let us know what lib you use to create proxies and we'll try to add support for this library in near future. + +More about Spring AOP + AspectJ read [here] (http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html) + +## Aspect weaving +Javanica supports two weaving modes: compile and runtime. Load time weaving hasn't been tested but it should work. If you tried LTW mode and got any problems then raise javanica issue or create pull request with fix. +- CTW. To use CTW mode you need to use specific jar version: **hystrix-javanica-ctw-X.Y.Z** . This jar is assembled with aspects compiled with using [AJC](https://eclipse.org/aspectj/doc/next/devguide/ajc-ref.html) compiler. If you will try to use regular hystrix-javanica-X.Y.Z with CTW then you get ``` NoSuchMethodError aspectOf() ``` at runtime from building with iajc. Also, you need to start your app with using java property: ```-DWeavingMode=compile```. +**NOTE**: Javanica depends on aspectj library and uses internal features of aspectj and these features aren't provided as a part of open API thus it can change from version to version. Javanica tested with latest aspectj version 1.8.7. If you updated aspectj version and noticed any issues then please don't hestitate to create new issue or contribute. +- RTW works, you can use regular hystrix-javanica-X.Y.Z +- LTM hasn't been tested but it should work fine. + + +# How to use + +## Hystrix command +### Synchronous Execution + +To run method as Hystrix command synchronously you need to annotate method with `@HystrixCommand` annotation, for example +```java +public class UserService { +... + @HystrixCommand + public User getUserById(String id) { + return userResource.getUserById(id); + } +} +... +``` +In example above the `getUserById` method will be processed [synchronously](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Synchronous-Execution) within new Hystrix command. +By default the name of **command key** is command method name: `getUserById`, default **group key** name is class name of annotated method: `UserService`. You can change it using necessary `@HystrixCommand` properties: + +```java + @HystrixCommand(groupKey="UserGroup", commandKey = "GetUserByIdCommand") + public User getUserById(String id) { + return userResource.getUserById(id); + } +``` +To set threadPoolKey use ```@HystrixCommand#threadPoolKey()``` + +### Asynchronous Execution + +To process Hystrix command asynchronously you should return an instance of `AsyncResult` in your command method as in the example below: +```java + @HystrixCommand + public Future getUserByIdAsync(final String id) { + return new AsyncResult() { + @Override + public User invoke() { + return userResource.getUserById(id); + } + }; + } +``` + +The return type of command method should be Future that indicates that a command should be executed [asynchronously] (https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Asynchronous-Execution). + +## Reactive Execution + +To perform "Reactive Execution" you should return an instance of `Observable` in your command method as in the example below: + +```java + @HystrixCommand + public Observable getUserById(final String id) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber observer) { + try { + if (!observer.isUnsubscribed()) { + observer.onNext(new User(id, name + id)); + observer.onCompleted(); + } + } catch (Exception e) { + observer.onError(e); + } + } + }); + } +``` +In addition to `Observable` Javanica supports the following RX types: `Single` and `Completable`. +Hystrix core supports only one RX type which is `Observable`, `HystrixObservableCommand` requires to return `Observable` therefore javanica transforms `Single` or `Completable` to `Observable` using `toObservable()` method for appropriate type and before returning the result to caller it translates `Observable` to either `Single` or `Completable` using `toSingle()` or `toCompletable()` correspondingly. + +HystrixObservable interface provides two methods: ```observe()``` - eagerly starts execution of the command the same as ``` HystrixCommand#queue()``` and ```HystrixCommand#execute()```; ```toObservable()``` - lazily starts execution of the command only once the Observable is subscribed to. To control this behaviour and swith between two modes ```@HystrixCommand``` provides specific parameter called ```observableExecutionMode```. +```@HystrixCommand(observableExecutionMode = EAGER)``` indicates that ```observe()``` method should be used to execute observable command +```@HystrixCommand(observableExecutionMode = LAZY)``` indicates that ```toObservable()``` should be used to execute observable command + +**NOTE: EAGER mode is used by default** + +## Fallback + +Graceful degradation can be achieved by declaring name of fallback method in `@HystrixCommand` like below: + +```java + @HystrixCommand(fallbackMethod = "defaultUser") + public User getUserById(String id) { + return userResource.getUserById(id); + } + + private User defaultUser(String id) { + return new User("def", "def"); + } +``` + +**_Its important to remember that Hystrix command and fallback should be placed in the same class and have same method signature (optional parameter for failed execution exception)_**. + +Fallback method can have any access modifier. Method `defaultUser` will be used to process fallback logic in a case of any errors. If you need to run fallback method `defaultUser` as separate Hystrix command then you need to annotate it with `HystrixCommand` annotation as below: +```java + @HystrixCommand(fallbackMethod = "defaultUser") + public User getUserById(String id) { + return userResource.getUserById(id); + } + + @HystrixCommand + private User defaultUser(String id) { + return new User(); + } +``` + +If fallback method was marked with `@HystrixCommand` then this fallback method (_defaultUser_) also can has own fallback method, as in the example below: +```java + @HystrixCommand(fallbackMethod = "defaultUser") + public User getUserById(String id) { + return userResource.getUserById(id); + } + + @HystrixCommand(fallbackMethod = "defaultUserSecond") + private User defaultUser(String id) { + return new User(); + } + + @HystrixCommand + private User defaultUserSecond(String id) { + return new User("def", "def"); + } +``` + +Javanica provides an ability to get execution exception (exception thrown that caused the failure of a command) within a fallback is being executed. A fallback method signature can be extended with an additional parameter in order to get an exception thrown by a command. Javanica exposes execution exception through additional parameter of fallback method. Execution exception is derived by calling method getExecutionException() as in vanilla hystrix. + +Example: + +```java + @HystrixCommand(fallbackMethod = "fallback1") + User getUserById(String id) { + throw new RuntimeException("getUserById command failed"); + } + + @HystrixCommand(fallbackMethod = "fallback2") + User fallback1(String id, Throwable e) { + assert "getUserById command failed".equals(e.getMessage()); + throw new RuntimeException("fallback1 failed"); + } + + @HystrixCommand(fallbackMethod = "fallback3") + User fallback2(String id) { + throw new RuntimeException("fallback2 failed"); + } + + @HystrixCommand(fallbackMethod = "staticFallback") + User fallback3(String id, Throwable e) { + assert "fallback2 failed".equals(e.getMessage()); + throw new RuntimeException("fallback3 failed"); + } + + User staticFallback(String id, Throwable e) { + assert "fallback3 failed".equals(e.getMessage()); + return new User("def", "def"); + } + + // test + @Test + public void test() { + assertEquals("def", getUserById("1").getName()); + } +``` +As you can see, the additional ```Throwable``` parameter is not mandatory and can be omitted or specified. +A fallback gets an exception thrown that caused a failure of parent, thus the ```fallback3``` gets exception thrown by ```fallback2```, no by ```getUserById``` command. + +### Async/Sync fallback. +A fallback can be async or sync, at certain cases it depends on command execution type, below listed all possible uses : + +**Supported** + +case 1: sync command, sync fallback + +```java + @HystrixCommand(fallbackMethod = "fallback") + User getUserById(String id) { + throw new RuntimeException("getUserById command failed"); + } + + @HystrixCommand + User fallback(String id) { + return new User("def", "def"); + } +``` + +case 2: async command, sync fallback + +```java + @HystrixCommand(fallbackMethod = "fallback") + Future getUserById(String id) { + throw new RuntimeException("getUserById command failed"); + } + + @HystrixCommand + User fallback(String id) { + return new User("def", "def"); + } +``` + +case 3: async command, async fallback +```java + @HystrixCommand(fallbackMethod = "fallbackAsync") + Future getUserById(String id) { + throw new RuntimeException("getUserById command failed"); + } + + @HystrixCommand + Future fallbackAsync(String id) { + return new AsyncResult() { + @Override + public User invoke() { + return new User("def", "def"); + } + }; + } +``` + +**Unsupported(prohibited)** + +case 1: sync command, async fallback command. This case isn't supported because in the essence a caller does not get a future buy calling ```getUserById``` and future is provided by fallback isn't available for a caller anyway, thus execution of a command forces to complete ```fallbackAsync``` before a caller gets a result, having said it turns out there is no benefits of async fallback execution. But it can be convenient if a fallback is used for both sync and async commands, if you see this case is very helpful and will be nice to have then create issue to add support for this case. + +```java + @HystrixCommand(fallbackMethod = "fallbackAsync") + User getUserById(String id) { + throw new RuntimeException("getUserById command failed"); + } + + @HystrixCommand + Future fallbackAsync(String id) { + return new AsyncResult() { + @Override + public User invoke() { + return new User("def", "def"); + } + }; + } +``` +case 2: sync command, async fallback. This case isn't supported for the same reason as for the case 1. + +```java + @HystrixCommand(fallbackMethod = "fallbackAsync") + User getUserById(String id) { + throw new RuntimeException("getUserById command failed"); + } + + Future fallbackAsync(String id) { + return new AsyncResult() { + @Override + public User invoke() { + return new User("def", "def"); + } + }; + } +``` + +Same restrictions are imposed on using observable feature in javanica. + +## Default fallback for class or concrete command +This feature allows to define default fallback for the whole class or concrete command. If you have a batch of commands with exactly the same fallback logic you still have to define a fallback method for every command because fallback method should have exactly the same signature as command does, consider the following code: + +```java + public class Service { + @RequestMapping(value = "/test1") + @HystrixCommand(fallbackMethod = "fallback") + public APIResponse test1(String param1) { + // some codes here + return APIResponse.success("success"); + } + + @RequestMapping(value = "/test2") + @HystrixCommand(fallbackMethod = "fallback") + public APIResponse test2() { + // some codes here + return APIResponse.success("success"); + } + + @RequestMapping(value = "/test3") + @HystrixCommand(fallbackMethod = "fallback") + public APIResponse test3(ObjectRequest obj) { + // some codes here + return APIResponse.success("success"); + } + + private APIResponse fallback(String param1) { + return APIResponse.failed("Server is busy"); + } + + private APIResponse fallback() { + return APIResponse.failed("Server is busy"); + } + + private APIResponse fallback(ObjectRequest obj) { + return APIResponse.failed("Server is busy"); + } + } +``` + +Default fallback feature allows to engage DRY principle and get rid of redundancy: + +```java + @DefaultProperties(defaultFallback = "fallback") + public class Service { + @RequestMapping(value = "/test1") + @HystrixCommand + public APIResponse test1(String param1) { + // some codes here + return APIResponse.success("success"); + } + + @RequestMapping(value = "/test2") + @HystrixCommand + public APIResponse test2() { + // some codes here + return APIResponse.success("success"); + } + + @RequestMapping(value = "/test3") + @HystrixCommand + public APIResponse test3(ObjectRequest obj) { + // some codes here + return APIResponse.success("success"); + } + + private APIResponse fallback() { + return APIResponse.failed("Server is busy"); + } + } +``` + +Default fallback method should not have any parameters except extra one to get execution exception and shouldn't throw any exceptions. +Below fallbacks listed in descending order of priority: + +1. command fallback defined using `fallbackMethod` property of `@HystrixCommand` +2. command default fallback defined using `defaultFallback` property of `@HystrixCommand` +3. class default fallback defined using `defaultFallback` property of `@DefaultProperties` + + +## Error Propagation +Based on [this](https://github.com/Netflix/Hystrix/wiki/How-To-Use#ErrorPropagation) description, `@HystrixCommand` has an ability to specify exceptions types which should be ignored. + +```java + @HystrixCommand(ignoreExceptions = {BadRequestException.class}) + public User getUserById(String id) { + return userResource.getUserById(id); + } +``` + +If `userResource.getUserById(id);` throws an exception that type is _BadRequestException_ then this exception will be wrapped in ``HystrixBadRequestException`` and re-thrown without triggering fallback logic. You don't need to do it manually, javanica will do it for you under the hood. + +It is worth noting that by default a caller will always get the root cause exception e.g. ``BadRequestException``, never ``HystrixBadRequestException`` or ``HystrixRuntimeException`` (except the case when executed code explicitly throws those exceptions). + +Optionally this exception un-wrapping can be disabled for ``HystrixRuntimeException`` by using ``raiseHystrixExceptions`` i.e. all exceptions that are not ignored are raised as the _cause_ of a ``HystrixRuntimeException``: + +```java + @HystrixCommand( + ignoreExceptions = {BadRequestException.class}, + raiseHystrixExceptions = {HystrixException.RUNTIME_EXCEPTION}) + public User getUserById(String id) { + return userResource.getUserById(id); + } +``` + +*Note*: If command has a fallback then only first exception that triggers fallback logic will be propagated to caller. Example: + +```java +class Service { + @HystrixCommand(fallbackMethod = "fallback") + Object command(Object o) throws CommandException { + throw new CommandException(); + } + + @HystrixCommand + Object fallback(Object o) throws FallbackException { + throw new FallbackException(); + } +} + +// in client code +{ + try { + service.command(null); + } catch (Exception e) { + assert CommandException.class.equals(e.getClass()) + } +} +``` + +## Request Cache + +Javanica provides specific annotations in order to enable and manage request caching. This annotations look very similar to [JSR107](https://github.com/jsr107/jsr107spec) but less extensive than those, by other hand Hystrix doesn't provide independent and complex caching system therefore there is no need to have such diversity of annotations as in JSR107. +Javanica has only three annotations dedicated for request caching. + + +| Annotation | Description | Properties | +| ------------- |-------------| -----| +| @CacheResult | Marks a methods that results should be cached for a Hystrix command.This annotation must be used in conjunction with HystrixCommand annotation. | cacheKeyMethod | +| @CacheRemove | Marks methods used to invalidate cache of a command. Generated cache key must be same as key generated within link CacheResult context | commandKey, cacheKeyMethod | +| @CacheKey | Marks a method argument as part of the cache key. If no arguments are marked all arguments are used. If _@CacheResult_ or _@CacheRemove_ annotation has specified _cacheKeyMethod_ then a method arguments will not be used to build cache key even if they annotated with _@CacheKey_ | value | + +**cacheKeyMethod** - a method name to be used to get a key for request caching. The command and cache key method should be placed in the same class and have same method signature except cache key method return type that should be _String_. +_cacheKeyMethod_ has higher priority than an arguments of a method, that means what actual arguments +of a method that annotated with _@CacheResult_ will not be used to generate cache key, instead specified +_cacheKeyMethod_ fully assigns to itself responsibility for cache key generation. +By default this returns empty string which means "do not use cache method. +You can consider _cacheKeyMethod_ as a replacement for common key generators (for example [JSR170-CacheKeyGenerator](https://github.com/jsr107/jsr107spec/blob/master/src/main/java/javax/cache/annotation/CacheKeyGenerator.java)) but with _cacheKeyMethod_ cache key generation becomes more convenient and simple. Not to be unfounded let's compare the two approaches: +JSR107 +```java + + @CacheRemove(cacheName = "getUserById", cacheKeyGenerator = UserCacheKeyGenerator.class) + @HystrixCommand + public void update(@CacheKey User user) { + storage.put(user.getId(), user); + } + + public static class UserCacheKeyGenerator implements HystrixCacheKeyGenerator { + @Override + public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext) { + CacheInvocationParameter cacheInvocationParameter = cacheKeyInvocationContext.getKeyParameters()[0]; + User user = (User) cacheInvocationParameter.getValue(); + return new DefaultHystrixGeneratedCacheKey(user.getId()); + } + } +``` + +Javanica cacheKeyMethod + +```java + @CacheRemove(commandKey = "getUserById", cacheKeyMethod=) + @HystrixCommand + public void update(User user) { + storage.put(user.getId(), user); + } + private String cacheKeyMethod(User user) { + return user.getId(); + } + +``` +or even just +```java + @CacheRemove(commandKey = "getUserById") + @HystrixCommand + public void update(@CacheKey("id") User user) { + storage.put(user.getId(), user); + } +``` +You don't need to create new classes, also approach with cacheKeyMethod helps during refactoring if you will give correct names for cache key methods. It is recommended to append prefix "cacheKeyMethod" to the real method name, for example: +```java +public User getUserById(@CacheKey String id); +``` +```java +private User getUserByIdCacheKeyMethod(String id); +``` +**Cache key generator** + +Javanica has only one cache key generator **HystrixCacheKeyGenerator** that generates a _HystrixGeneratedCacheKey_ based on _CacheInvocationContext_. Implementation is thread-safe. +Parameters of an annotated method are selected by the following rules: +- If no parameters are annotated with _@CacheKey_ then all parameters are included +- If one or more _@CacheKey_ annotations exist only those parameters with the _@CacheKey_ annotation are included + +**Note**: If _CacheResult_ or _CacheRemove_ annotation has specified **cacheKeyMethod** then a method arguments **will not be used to build cache key** even if they annotated with _CacheKey_. + +**@CacheKey and value property** +This annotation has one property by default that allows specify name of a certain argument property. for example: ```@CacheKey("id") User user```, or in case composite property: ```@CacheKey("profile.name") User user```. Null properties are ignored, i.e. if ```profile``` is ```null``` then result of ```@CacheKey("profile.name") User user``` will be empty string. + +Examples: + +```java + @CacheResult + @HystrixCommand + public User getUserById(@CacheKey String id) { + return storage.get(id); + } + + // -------------------------------------------------- + @CacheResult(cacheKeyMethod = "getUserByNameCacheKey") + @HystrixCommand + public User getUserByName(String name) { + return storage.getByName(name); + } + private Long getUserByNameCacheKey(String name) { + return name; + } + // -------------------------------------------------- + @CacheResult + @HystrixCommand + public void getUserByProfileName(@CacheKey("profile.email") User user) { + storage.getUserByProfileName(user.getProfile().getName()); + } + +``` + +**Get-Set-Get pattern** +To get more about this pattern you can read [this](https://github.com/Netflix/Hystrix/wiki/How-To-Use#get-set-get-with-request-cache-invalidation) chapter +Example: +```java + public class UserService { + @CacheResult + @HystrixCommand + public User getUserById(@CacheKey String id) { // GET + return storage.get(id); + } + + @CacheRemove(commandKey = "getUserById") + @HystrixCommand + public void update(@CacheKey("id") User user) { // SET + storage.put(user.getId(), user); + } + } + + // test app + + public void test(){ + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + + user = new User("1", "new_name"); + userService.update(user); // update the user + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "update" + // method was invoked and a cache for "getUserById" command was flushed + // so the response shouldn't be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } +``` + +**Note**: You can use @CacheRemove annotation in conjunction with @HystrixCommand or without. If you want annotate not command method with @CacheRemove annotation then you need to add HystrixCacheAspect aspect to your configuration: + +```xml + + ... + + ... + + + + + + + +``` + +## Configuration +### Command Properties + +Command properties can be set using @HystrixCommand's 'commandProperties' like below: + +```java + @HystrixCommand(commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500") + }) + public User getUserById(String id) { + return userResource.getUserById(id); + } +``` + +Javanica dynamically sets properties using Hystrix ConfigurationManager. +For the example above Javanica behind the scenes performs next action: +```java +ConfigurationManager.getConfigInstance().setProperty("hystrix.command.getUserById.execution.isolation.thread.timeoutInMilliseconds", "500"); +``` +More about Hystrix command properties [command](https://github.com/Netflix/Hystrix/wiki/Configuration#wiki-CommandExecution) and [fallback](https://github.com/Netflix/Hystrix/wiki/Configuration#wiki-CommandFallback) + +ThreadPoolProperties can be set using @HystrixCommand's 'threadPoolProperties' like below: + +```java + @HystrixCommand(commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500") + }, + threadPoolProperties = { + @HystrixProperty(name = "coreSize", value = "30"), + @HystrixProperty(name = "maxQueueSize", value = "101"), + @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), + @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"), + @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"), + @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440") + }) + public User getUserById(String id) { + return userResource.getUserById(id); + } +``` + +### DefaultProperties +``@DefaultProperties`` is class (type) level annotation that allows to default commands properties such as ``groupKey``, ``threadPoolKey``, ``commandProperties``, ``threadPoolProperties``, ``ignoreExceptions`` and ``raiseHystrixExceptions``. Properties specified using this annotation will be used by default for each hystrix command defined within annotated class unless a command specifies those properties explicitly using corresponding ``@HystrixCommand`` parameters. +Example: + +```java +@DefaultProperties(groupKey = "DefaultGroupKey") +class Service { + @HystrixCommand // hystrix command group key is 'DefaultGroupKey' + public Object commandInheritsDefaultProperties() { + return null; + } + @HystrixCommand(groupKey = "SpecificGroupKey") // command overrides default group key + public Object commandOverridesGroupKey() { + return null; + } +} +``` + +## Hystrix collapser + +Suppose you have some command which calls should be collapsed in one backend call. For this goal you can use ```@HystrixCollapser``` annotation. + +Example: +```java + + /** Asynchronous Execution */ + @HystrixCollapser(batchMethod = "getUserByIds") + public Future getUserByIdAsync(String id) { + return null; + } + + /** Reactive Execution */ + @HystrixCollapser(batchMethod = "getUserByIds") + public Observable getUserByIdReact(String id) { + return null; + } + + @HystrixCommand + public List getUserByIds(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + return users; + } + + // Async + Future f1 = userService.getUserByIdAsync("1"); + Future f2 = userService.getUserByIdAsync("2"); + Future f3 = userService.getUserByIdAsync("3"); + Future f4 = userService.getUserByIdAsync("4"); + Future f5 = userService.getUserByIdAsync("5"); + + // Reactive + Observable u1 = getUserByIdReact("1"); + Observable u2 = getUserByIdReact("2"); + Observable u3 = getUserByIdReact("3"); + Observable u4 = getUserByIdReact("4"); + Observable u5 = getUserByIdReact("5"); + + // Materialize reactive commands + Iterable users = Observables.merge(u1, u2, u3, u4, u5).toBlocking().toIterable(); +``` +A method annotated with ```@HystrixCollapser``` annotation can return any value with compatible type, it does not affect the result of collapser execution, collapser method can even return ```null``` or another stub. +There are several rules applied for methods signatures. + +1. Collapser method must have one argument of any type, desired a wrapper of a primitive type like Integer, Long, String and etc. +2. A batch method must have one argument with type java.util.List parameterized with corresponding type, that's if a type of collapser argument is ```Integer``` then type of batch method argument must be ```List```. +3. Return type of batch method must be java.util.List parameterized with corresponding type, that's if a return type of collapser method is ```User``` then a return type of batch command must be ```List```. + +**Convention for batch method behavior** + +The size of response collection must be equal to the size of request collection. + +```java + @HystrixCommand + public List getUserByIds(List ids); // batch method + + List ids = List("1", "2", "3"); + getUserByIds(ids).size() == ids.size(); +``` +Order of elements in response collection must be same as in request collection. + +``` + @HystrixCommand + public List getUserByIds(List ids); // batch method + + List ids = List("1", "2", "3"); + List users = getUserByIds(ids); + System.out.println(users); + // output + User: id=1 + User: id=2 + User: id=3 +``` + +**Why order of elements of request and response collections is important?** + +The reason of this is in reducing logic, basically request elements are mapped one-to-one to response elements. Thus if order of elements of request collection is different then the result of execution can be unpredictable. + +**Deduplication batch command request parameters**. + +In some cases your batch method can depend on behavior of third-party service or library that skips duplicates in a request. It can be a rest service that expects unique values and ignores duplicates. In this case the size of elements in request collection can be different from size of elements in response collection. It violates one of the behavior principle. To fix it you need manually map request to response, for example: + +```java +// hava 8 +@HystrixCommand +List batchMethod(List ids){ +// ids = [1, 2, 2, 3] +List users = restClient.getUsersByIds(ids); +// users = [User{id='1', name='user1'}, User{id='2', name='user2'}, User{id='3', name='user3'}] +List response = ids.stream().map(it -> users.stream() + .filter(u -> u.getId().equals(it)).findFirst().get()) + .collect(Collectors.toList()); +// response = [User{id='1', name='user1'}, User{id='2', name='user2'}, User{id='2', name='user2'}, User{id='3', name='user3'}] +return response; +``` + +Same case if you want to remove duplicate elements from request collection before a service call. +Example: +```java +// hava 8 +@HystrixCommand +List batchMethod(List ids){ +// ids = [1, 2, 2, 3] +List uniqueIds = ids.stream().distinct().collect(Collectors.toList()); +// uniqueIds = [1, 2, 3] +List users = restClient.getUsersByIds(uniqueIds); +// users = [User{id='1', name='user1'}, User{id='2', name='user2'}, User{id='3', name='user3'}] +List response = ids.stream().map(it -> users.stream() + .filter(u -> u.getId().equals(it)).findFirst().get()) + .collect(Collectors.toList()); +// response = [User{id='1', name='user1'}, User{id='2', name='user2'}, User{id='2', name='user2'}, User{id='3', name='user3'}] +return response; +``` +To set collapser [properties](https://github.com/Netflix/Hystrix/wiki/Configuration#Collapser) use `@HystrixCollapser#collapserProperties` + +Read more about Hystrix request collapsing [here] (https://github.com/Netflix/Hystrix/wiki/How-it-Works#wiki-RequestCollapsing) + +**Collapser error processing** +Batch command can have a fallback method. +Example: + +```java + @HystrixCollapser(batchMethod = "getUserByIdsWithFallback") + public Future getUserByIdWithFallback(String id) { + return null; + } + + @HystrixCommand(fallbackMethod = "getUserByIdsFallback") + public List getUserByIdsWithFallback(List ids) { + throw new RuntimeException("not found"); + } + + + @HystrixCommand + private List getUserByIdsFallback(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + return users; + } +``` + + +#Development Status and Future +Please create an issue if you need a feature or you detected some bugs. Thanks + +**Note**: Javanica 1.4.+ is updated more frequently than 1.3.+ hence 1.4+ is more stable. + +**It's recommended to use Javanica 1.4.+** diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-javanica/build.gradle new file mode 100644 index 0000000..3febf6d --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/build.gradle @@ -0,0 +1,119 @@ +apply plugin: 'osgi' + +configurations { + ajtools + ajcTestCompile.extendsFrom testCompile + ajcTestRuntime.extendsFrom testRuntime + ajc +} + + +sourceSets { + ajcTest { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + } + + ajc { + def main = sourceSets.main + java.srcDirs = main.java.srcDirs + resources.srcDirs = main.resources.srcDirs + compileClasspath = main.compileClasspath + runtimeClasspath = main.runtimeClasspath + } +} + + + +compileAjcTestJava { + + sourceCompatibility = "1.6" + targetCompatibility = "1.6" + + doLast { + ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajtools.asPath) + ant.iajc(source: "1.6", target: "1.6", + destDir: "${sourceSets.ajcTest.output.classesDir.absolutePath}", maxmem: "512m", fork: "true", + classpath: "${sourceSets.main.output.classesDir.absolutePath};${configurations.testCompile.asPath}") + { + sourceroots { + files("src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj", "src/test/java/com/netflix/hystrix/contrib/javanica/test/common", "src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj").each { + File file -> pathelement(location: file.absolutePath) + } + } + } + } + +} + +compileAjcJava { + sourceCompatibility = "1.6" + targetCompatibility = "1.6" + + doLast { + ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajtools.asPath) + ant.iajc(source: "${sourceCompatibility}", target: "${targetCompatibility}", + destDir: "${sourceSets.ajc.output.classesDir.absolutePath}", maxmem: "512m", fork: "true", "showWeaveInfo": "true", + classpath: "${configurations.compile.asPath}") + + { + sourceroots { + sourceSets.ajc.java.srcDirs.each { + pathelement(location: it.absolutePath) + + } + } + } + } +} + +task ajcTest(type: Test) { + testClassesDir = sourceSets.ajcTest.output.classesDir + classpath = sourceSets.ajcTest.runtimeClasspath + if (System.getProperty('DEBUG', 'false') == 'true') { + jvmArgs '-Xdebug', + '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9009' + } +} + +check.dependsOn test, ajcTest + + +ext { + aspectjVersion = '1.8.6' + springframeworkVesion = '4.3.2.RELEASE' +} + + +task ajcJar(type: Jar) { + archiveName = "${project.name}" + "-ctw-${version}.jar" + destinationDir = file("${buildDir}/libs") + from sourceSets.ajc.output +} + +assemble.dependsOn(jar, ajcJar) + +dependencies { + compileApi project(':hystrix-core') + ajtools "org.aspectj:aspectjtools:$aspectjVersion" + testRuntime "org.aspectj:aspectjrt:$aspectjVersion" + compileApi "org.aspectj:aspectjweaver:$aspectjVersion" + compile "org.aspectj:aspectjrt:$aspectjVersion" + + compileApi 'com.google.guava:guava:15.0' + compile 'org.apache.commons:commons-lang3:3.1' + compileApi 'com.google.code.findbugs:jsr305:2.0.0' + compile 'org.ow2.asm:asm:5.0.4' + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile 'pl.pragmatists:JUnitParams:1.0.5' + testCompile project(':hystrix-junit') + testCompile "org.springframework:spring-core:$springframeworkVesion" + testCompile "org.springframework:spring-context:$springframeworkVesion" + testCompile "org.springframework:spring-aop:$springframeworkVesion" + testCompile "org.springframework:spring-test:$springframeworkVesion" + testCompile 'cglib:cglib:3.1' + testCompile 'org.mockito:mockito-all:1.9.5' + testCompile 'log4j:log4j:1.2.17' + testCompile 'org.slf4j:slf4j-log4j12:1.7.7' + testCompile 'com.tngtech.java:junit-dataprovider:1.10.2' +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/cache/CacheTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/cache/CacheTest.java new file mode 100644 index 0000000..3323ab1 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/cache/CacheTest.java @@ -0,0 +1,36 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.cache; + +import com.netflix.hystrix.contrib.javanica.test.common.cache.BasicCacheTest; +import org.junit.BeforeClass; + +/** + * Created by dmgcodevil + */ +public class CacheTest extends BasicCacheTest { + @BeforeClass + public static void setUpEnv() { + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + UserService userService = new UserService(); + userService.init(); + return userService; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/collapser/CollapserTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/collapser/CollapserTest.java new file mode 100644 index 0000000..4a8575f --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/collapser/CollapserTest.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.collapser; + +import com.netflix.hystrix.contrib.javanica.test.common.collapser.BasicCollapserTest; +import org.junit.BeforeClass; + +/** + * Created by dmgcodevil + */ +public class CollapserTest extends BasicCollapserTest { + @BeforeClass + public static void setUpEnv() { + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + return new UserService(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/command/CommandTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/command/CommandTest.java new file mode 100644 index 0000000..cdff284 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/command/CommandTest.java @@ -0,0 +1,44 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.command; + +import com.netflix.hystrix.contrib.javanica.test.common.command.BasicCommandTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import org.junit.BeforeClass; + + +public class CommandTest extends BasicCommandTest { + + @BeforeClass + public static void setUpEnv(){ + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + return new UserService(); + } + + @Override + protected AdvancedUserService createAdvancedUserServiceService() { + return new AdvancedUserService(); + } + + @Override + protected GenericService createGenericUserService() { + return new GenericUserService(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/configuration/collapser/CollapserPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/configuration/collapser/CollapserPropertiesTest.java new file mode 100644 index 0000000..499f95b --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/configuration/collapser/CollapserPropertiesTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.configuration.collapser; + +import com.netflix.hystrix.contrib.javanica.test.common.configuration.collapser.BasicCollapserPropertiesTest; +import org.junit.BeforeClass; + +/** + * Created by dmgcodevil + */ +public class CollapserPropertiesTest extends BasicCollapserPropertiesTest { + + @BeforeClass + public static void setUpEnv() { + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + return new UserService(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/configuration/command/CommandPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/configuration/command/CommandPropertiesTest.java new file mode 100644 index 0000000..ac31202 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/configuration/command/CommandPropertiesTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.configuration.command; + +import com.netflix.hystrix.contrib.javanica.test.common.configuration.command.BasicCommandPropertiesTest; +import org.junit.BeforeClass; + +/** + * Created by dmgcodevil + */ +public class CommandPropertiesTest extends BasicCommandPropertiesTest { + + @BeforeClass + public static void setUpEnv() { + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + return new UserService(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/error/ErrorPropagationTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/error/ErrorPropagationTest.java new file mode 100644 index 0000000..f818553 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/error/ErrorPropagationTest.java @@ -0,0 +1,36 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.error; + +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicErrorPropagationTest; +import org.junit.BeforeClass; + +/** + * Created by dmgcodevil + */ +public class ErrorPropagationTest extends BasicErrorPropagationTest { + + @BeforeClass + public static void setUpEnv() { + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + return new UserService(); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/error/ObservableErrorPropagationTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/error/ObservableErrorPropagationTest.java new file mode 100644 index 0000000..f51fa45 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/error/ObservableErrorPropagationTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.error; + +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicErrorPropagationTest; +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicObservableErrorPropagationTest; +import org.junit.BeforeClass; + +/** + * Created by dmgcodevil + */ +public class ObservableErrorPropagationTest extends BasicObservableErrorPropagationTest { + + @BeforeClass + public static void setUpEnv() { + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + return new UserService(); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/fallback/CommandFallbackTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/fallback/CommandFallbackTest.java new file mode 100644 index 0000000..f99870e --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/fallback/CommandFallbackTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.fallback; + +import com.netflix.hystrix.contrib.javanica.test.common.fallback.BasicCommandFallbackTest; +import org.junit.BeforeClass; + +/** + * Created by dmgcodevil + */ +public class CommandFallbackTest extends BasicCommandFallbackTest { + + @BeforeClass + public static void setUpEnv() { + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + return new UserService(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/observable/ObservableTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/observable/ObservableTest.java new file mode 100644 index 0000000..003c482 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/java/com/netflix/hystrix/contrib/javanica/test/aspectj/observable/ObservableTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 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.contrib.javanica.test.aspectj.observable; + +import com.netflix.hystrix.contrib.javanica.test.common.observable.BasicObservableTest; +import org.junit.BeforeClass; + +/** + * Created by dmgcodevil + */ +public class ObservableTest extends BasicObservableTest { + + @BeforeClass + public static void setUpEnv() { + System.setProperty("weavingMode", "compile"); + } + + @Override + protected UserService createUserService() { + return new UserService(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/resources/dummy.txt b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/resources/dummy.txt new file mode 100644 index 0000000..936ad77 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/resources/dummy.txt @@ -0,0 +1,16 @@ +==== + Copyright 2016 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. +==== + diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/resources/log4j.properties b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/resources/log4j.properties new file mode 100644 index 0000000..bc1caf1 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/ajcTest/resources/log4j.properties @@ -0,0 +1,26 @@ +# +# Copyright 2016 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. +# + +# Define the root logger with appender console +log4j.rootLogger = ERROR, CONSOLE + +# Define the console appender +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.File=${log}/log.out + +# Define the layout for console appender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.conversionPattern=%m%n \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java new file mode 100644 index 0000000..fa80702 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java @@ -0,0 +1,92 @@ +/** + * Copyright 2016 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.contrib.javanica.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to specify default parameters for + * hystrix commands (methods annotated with {@code @HystrixCommand} annotation). + * + * @author dmgcodevil + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface DefaultProperties { + + /** + * Specifies default group key used for each hystrix command by default unless a command specifies group key explicitly. + * For additional info about this property see {@link HystrixCommand#groupKey()}. + * + * @return default group key + */ + String groupKey() default ""; + + /** + * Specifies default thread pool key used for each hystrix command by default unless a command specifies thread pool key explicitly. + * For additional info about this property see {@link HystrixCommand#threadPoolKey()} + * + * @return default thread pool + */ + String threadPoolKey() default ""; + + /** + * Specifies command properties that will be used for + * each hystrix command be default unless command properties explicitly specified in @HystrixCommand. + * + * @return command properties + */ + HystrixProperty[] commandProperties() default {}; + + /** + * Specifies thread pool properties that will be used for + * each hystrix command be default unless thread pool properties explicitly specified in @HystrixCommand. + * + * @return thread pool properties + */ + HystrixProperty[] threadPoolProperties() default {}; + + /** + * Defines exceptions which should be ignored. + * Optionally these can be wrapped in HystrixRuntimeException if raiseHystrixExceptions contains RUNTIME_EXCEPTION. + * + * @return exceptions to ignore + */ + Class[] ignoreExceptions() default {}; + + /** + * When includes RUNTIME_EXCEPTION, any exceptions that are not ignored are wrapped in HystrixRuntimeException. + * + * @return exceptions to wrap + */ + HystrixException[] raiseHystrixExceptions() default {}; + + /** + * Specifies default fallback method for each command in the given class. Every command within the class should + * have a return type which is compatible with default fallback method return type. + * note: default fallback method cannot have parameters. + * + * @return the name of default fallback method + */ + String defaultFallback() default ""; +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCollapser.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCollapser.java new file mode 100644 index 0000000..21b4da2 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCollapser.java @@ -0,0 +1,101 @@ +/** + * Copyright 2012 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.contrib.javanica.annotation; + +import com.netflix.hystrix.HystrixCollapser.Scope; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to collapse some commands into a single backend dependency call. + * This annotation should be used together with {@link HystrixCommand} annotation. + *

+ * Example: + *

+ *     @HystrixCollapser(batchMethod = "getUserByIds"){
+ *          public Future getUserById(String id) {
+ *          return null;
+ * }
+ *  @HystrixCommand
+ *      public List getUserByIds(List ids) {
+ *          List users = new ArrayList();
+ *          for (String id : ids) {
+ *              users.add(new User(id, "name: " + id));
+ *          }
+ *      return users;
+ * }
+ *   
+ * + * A method annotated with {@link HystrixCollapser} annotation can return any + * value with compatible type, it does not affect the result of collapser execution, + * collapser method can even return {@code null} or another stub. + * Pay attention that if a collapser method returns parametrized Future then generic type must be equal to generic type of List, + * for instance: + *
+ *     Future - return type of collapser method
+ *     List - return type of batch command method
+ * 
+ *

+ * Note: batch command method must be annotated with {@link HystrixCommand} annotation. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface HystrixCollapser { + + /** + * Specifies a collapser key. + *

+ * default => the name of annotated method. + * + * @return collapser key. + */ + String collapserKey() default ""; + + /** + * Method name of batch command. + *

+ * Method must have the following signature: + *

+     *     java.util.List method(java.util.List)
+     * 
+ * NOTE: batch method can have only one argument. + * + * @return method name of batch command + */ + String batchMethod(); + + /** + * Defines what scope the collapsing should occur within. + *

+ * default => the {@link Scope#REQUEST}. + * + * @return {@link Scope} + */ + Scope scope() default Scope.REQUEST; + + /** + * Specifies collapser properties. + * + * @return collapser properties + */ + HystrixProperty[] collapserProperties() default {}; + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java new file mode 100644 index 0000000..cad4398 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java @@ -0,0 +1,134 @@ +/** + * Copyright 2012 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.contrib.javanica.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * This annotation used to specify some methods which should be processes as hystrix commands. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface HystrixCommand { + + /** + * The command group key is used for grouping together commands such as for reporting, + * alerting, dashboards or team/library ownership. + *

+ * default => the runtime class name of annotated method + * + * @return group key + */ + String groupKey() default ""; + + /** + * Hystrix command key. + *

+ * default => the name of annotated method. for example: + * + * ... + * @HystrixCommand + * public User getUserById(...) + * ... + * the command name will be: 'getUserById' + * + * + * @return command key + */ + String commandKey() default ""; + + /** + * The thread-pool key is used to represent a + * HystrixThreadPool for monitoring, metrics publishing, caching and other such uses. + * + * @return thread pool key + */ + String threadPoolKey() default ""; + + /** + * Specifies a method to process fallback logic. + * A fallback method should be defined in the same class where is HystrixCommand. + * Also a fallback method should have same signature to a method which was invoked as hystrix command. + * for example: + * + * @HystrixCommand(fallbackMethod = "getByIdFallback") + * public String getById(String id) {...} + * + * private String getByIdFallback(String id) {...} + * + * Also a fallback method can be annotated with {@link HystrixCommand} + *

+ * default => see {@link com.netflix.hystrix.contrib.javanica.command.GenericCommand#getFallback()} + * + * @return method name + */ + String fallbackMethod() default ""; + + /** + * Specifies command properties. + * + * @return command properties + */ + HystrixProperty[] commandProperties() default {}; + + /** + * Specifies thread pool properties. + * + * @return thread pool properties + */ + HystrixProperty[] threadPoolProperties() default {}; + + /** + * Defines exceptions which should be ignored. + * Optionally these can be wrapped in HystrixRuntimeException if raiseHystrixExceptions contains RUNTIME_EXCEPTION. + * + * @return exceptions to ignore + */ + Class[] ignoreExceptions() default {}; + + /** + * Specifies the mode that should be used to execute hystrix observable command. + * For more information see {@link ObservableExecutionMode}. + * + * @return observable execution mode + */ + ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER; + + /** + * When includes RUNTIME_EXCEPTION, any exceptions that are not ignored are wrapped in HystrixRuntimeException. + * + * @return exceptions to wrap + */ + HystrixException[] raiseHystrixExceptions() default {}; + + /** + * Specifies default fallback method for the command. If both {@link #fallbackMethod} and {@link #defaultFallback} + * methods are specified then specific one is used. + * note: default fallback method cannot have parameters, return type should be compatible with command return type. + * + * @return the name of default fallback method + */ + String defaultFallback() default ""; +} + diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixException.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixException.java new file mode 100644 index 0000000..ff83311 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixException.java @@ -0,0 +1,8 @@ +package com.netflix.hystrix.contrib.javanica.annotation; + +/** + * Created by Mike Cowan + */ +public enum HystrixException { + RUNTIME_EXCEPTION, +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixProperty.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixProperty.java new file mode 100644 index 0000000..4ef2f5d --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixProperty.java @@ -0,0 +1,47 @@ +/** + * Copyright 2012 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.contrib.javanica.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation allows specify Hystrix command properties in the following format: + * property name = property value. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface HystrixProperty { + + /** + * Property name. + * + * @return name + */ + String name(); + + /** + * Property value + * + * @return value + */ + String value(); + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/ObservableExecutionMode.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/ObservableExecutionMode.java new file mode 100644 index 0000000..a8dad26 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/ObservableExecutionMode.java @@ -0,0 +1,42 @@ +/** + * 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.contrib.javanica.annotation; + +import com.netflix.hystrix.HystrixObservable; +import rx.Observable; + +/** + * Hystrix observable command can be executed in two different ways: + * eager - {@link HystrixObservable#observe()}, + * lazy - {@link HystrixObservable#toObservable()}. + *

+ * This enum is used to specify desire execution mode. + *

+ * Created by dmgcodevil. + */ +public enum ObservableExecutionMode { + + /** + * This mode lazily starts execution of the command only once the {@link Observable} is subscribed to. + */ + LAZY, + + /** + * This mode eagerly starts execution of the command the same as {@link com.netflix.hystrix.HystrixCommand#queue()} + * and {@link com.netflix.hystrix.HystrixCommand#execute()}. + */ + EAGER +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java new file mode 100644 index 0000000..eaf150b --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java @@ -0,0 +1,68 @@ +/** + * 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.contrib.javanica.aop.aspectj; + + +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory; +import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.command.ExecutionType; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import org.apache.commons.lang3.Validate; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +import java.lang.reflect.Method; + +import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget; +import static com.netflix.hystrix.contrib.javanica.utils.EnvUtils.isCompileWeaving; +import static com.netflix.hystrix.contrib.javanica.utils.ajc.AjcUtils.getAjcMethodAroundAdvice; + +/** + * AspectJ aspect to process methods which annotated with annotations from + * com.netflix.hystrix.contrib.javanica.cache.annotation package. + * + * @author dmgcodevil + */ +@Aspect +public class HystrixCacheAspect { + + @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove) && !@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") + public void cacheRemoveAnnotationPointcut() { + } + + @Around("cacheRemoveAnnotationPointcut()") + public Object methodsAnnotatedWithCacheRemove(final ProceedingJoinPoint joinPoint) throws Throwable { + Method method = getMethodFromTarget(joinPoint); + Object obj = joinPoint.getTarget(); + Object[] args = joinPoint.getArgs(); + Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint); + MetaHolder metaHolder = MetaHolder.builder() + .args(args).method(method).obj(obj) + .executionType(ExecutionType.SYNCHRONOUS) + .ajcMethod(isCompileWeaving() ? getAjcMethodAroundAdvice(obj.getClass(), method) : null) + .build(); + CacheInvocationContext context = CacheInvocationContextFactory + .createCacheRemoveInvocationContext(metaHolder); + HystrixRequestCacheManager.getInstance().clearCache(context); + return joinPoint.proceed(); + } + + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java new file mode 100644 index 0000000..47cf74c --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java @@ -0,0 +1,352 @@ +/** + * Copyright 2012 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.contrib.javanica.aop.aspectj; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixException; +import com.netflix.hystrix.contrib.javanica.command.CommandExecutor; +import com.netflix.hystrix.contrib.javanica.command.ExecutionType; +import com.netflix.hystrix.contrib.javanica.command.HystrixCommandFactory; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; +import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException; +import com.netflix.hystrix.contrib.javanica.utils.AopUtils; +import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; +import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import rx.Completable; +import rx.Observable; +import rx.Single; +import rx.functions.Func1; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; + +import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getDeclaredMethod; +import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget; +import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodInfo; +import static com.netflix.hystrix.contrib.javanica.utils.EnvUtils.isCompileWeaving; +import static com.netflix.hystrix.contrib.javanica.utils.ajc.AjcUtils.getAjcMethodAroundAdvice; + +/** + * AspectJ aspect to process methods which annotated with {@link HystrixCommand} annotation. + */ +@Aspect +public class HystrixCommandAspect { + + private static final Map META_HOLDER_FACTORY_MAP; + + static { + META_HOLDER_FACTORY_MAP = ImmutableMap.builder() + .put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory()) + .put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory()) + .build(); + } + + @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") + + public void hystrixCommandAnnotationPointcut() { + } + + @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)") + public void hystrixCollapserAnnotationPointcut() { + } + + @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()") + public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable { + Method method = getMethodFromTarget(joinPoint); + Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint); + if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) { + throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " + + "annotations at the same time"); + } + MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method)); + MetaHolder metaHolder = metaHolderFactory.create(joinPoint); + HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder); + ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ? + metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType(); + + Object result; + try { + if (!metaHolder.isObservable()) { + result = CommandExecutor.execute(invokable, executionType, metaHolder); + } else { + result = executeObservable(invokable, executionType, metaHolder); + } + } catch (HystrixBadRequestException e) { + throw e.getCause() != null ? e.getCause() : e; + } catch (HystrixRuntimeException e) { + throw hystrixRuntimeExceptionToThrowable(metaHolder, e); + } + return result; + } + + private Object executeObservable(HystrixInvokable invokable, ExecutionType executionType, final MetaHolder metaHolder) { + return mapObservable(((Observable) CommandExecutor.execute(invokable, executionType, metaHolder)) + .onErrorResumeNext(new Func1() { + @Override + public Observable call(Throwable throwable) { + if (throwable instanceof HystrixBadRequestException) { + return Observable.error(throwable.getCause()); + } else if (throwable instanceof HystrixRuntimeException) { + HystrixRuntimeException hystrixRuntimeException = (HystrixRuntimeException) throwable; + return Observable.error(hystrixRuntimeExceptionToThrowable(metaHolder, hystrixRuntimeException)); + } + return Observable.error(throwable); + } + }), metaHolder); + } + + private Object mapObservable(Observable observable, final MetaHolder metaHolder) { + if (Completable.class.isAssignableFrom(metaHolder.getMethod().getReturnType())) { + return observable.toCompletable(); + } else if (Single.class.isAssignableFrom(metaHolder.getMethod().getReturnType())) { + return observable.toSingle(); + } + return observable; + } + + private Throwable hystrixRuntimeExceptionToThrowable(MetaHolder metaHolder, HystrixRuntimeException e) { + if (metaHolder.raiseHystrixExceptionsContains(HystrixException.RUNTIME_EXCEPTION)) { + return e; + } + return getCause(e); + } + + private Throwable getCause(HystrixRuntimeException e) { + if (e.getFailureType() != HystrixRuntimeException.FailureType.COMMAND_EXCEPTION) { + return e; + } + + Throwable cause = e.getCause(); + + // latest exception in flow should be propagated to end user + if (e.getFallbackException() instanceof FallbackInvocationException) { + cause = e.getFallbackException().getCause(); + if (cause instanceof HystrixRuntimeException) { + cause = getCause((HystrixRuntimeException) cause); + } + } else if (cause instanceof CommandActionExecutionException) { // this situation is possible only if a callee throws an exception which type extends Throwable directly + CommandActionExecutionException commandActionExecutionException = (CommandActionExecutionException) cause; + cause = commandActionExecutionException.getCause(); + } + + return Optional.fromNullable(cause).or(e); + } + + /** + * A factory to create MetaHolder depending on {@link HystrixPointcutType}. + */ + private static abstract class MetaHolderFactory { + public MetaHolder create(final ProceedingJoinPoint joinPoint) { + Method method = getMethodFromTarget(joinPoint); + Object obj = joinPoint.getTarget(); + Object[] args = joinPoint.getArgs(); + Object proxy = joinPoint.getThis(); + return create(proxy, method, obj, args, joinPoint); + } + + public abstract MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint); + + MetaHolder.Builder metaHolderBuilder(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) { + MetaHolder.Builder builder = MetaHolder.builder() + .args(args).method(method).obj(obj).proxyObj(proxy) + .joinPoint(joinPoint); + + setFallbackMethod(builder, obj.getClass(), method); + builder = setDefaultProperties(builder, obj.getClass(), joinPoint); + return builder; + } + } + + private static class CollapserMetaHolderFactory extends MetaHolderFactory { + + @Override + public MetaHolder create(Object proxy, Method collapserMethod, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) { + HystrixCollapser hystrixCollapser = collapserMethod.getAnnotation(HystrixCollapser.class); + if (collapserMethod.getParameterTypes().length > 1 || collapserMethod.getParameterTypes().length == 0) { + throw new IllegalStateException("Collapser method must have one argument: " + collapserMethod); + } + + Method batchCommandMethod = getDeclaredMethod(obj.getClass(), hystrixCollapser.batchMethod(), List.class); + + if (batchCommandMethod == null) + throw new IllegalStateException("batch method is absent: " + hystrixCollapser.batchMethod()); + + Class batchReturnType = batchCommandMethod.getReturnType(); + Class collapserReturnType = collapserMethod.getReturnType(); + boolean observable = collapserReturnType.equals(Observable.class); + + if (!collapserMethod.getParameterTypes()[0] + .equals(getFirstGenericParameter(batchCommandMethod.getGenericParameterTypes()[0]))) { + throw new IllegalStateException("required batch method for collapser is absent, wrong generic type: expected " + + obj.getClass().getCanonicalName() + "." + + hystrixCollapser.batchMethod() + "(java.util.List<" + collapserMethod.getParameterTypes()[0] + ">), but it's " + + getFirstGenericParameter(batchCommandMethod.getGenericParameterTypes()[0])); + } + + final Class collapserMethodReturnType = getFirstGenericParameter( + collapserMethod.getGenericReturnType(), + Future.class.isAssignableFrom(collapserReturnType) || Observable.class.isAssignableFrom(collapserReturnType) ? 1 : 0); + + Class batchCommandActualReturnType = getFirstGenericParameter(batchCommandMethod.getGenericReturnType()); + if (!collapserMethodReturnType + .equals(batchCommandActualReturnType)) { + throw new IllegalStateException("Return type of batch method must be java.util.List parametrized with corresponding type: expected " + + "(java.util.List<" + collapserMethodReturnType + ">)" + obj.getClass().getCanonicalName() + "." + + hystrixCollapser.batchMethod() + "(java.util.List<" + collapserMethod.getParameterTypes()[0] + ">), but it's " + + batchCommandActualReturnType); + } + + HystrixCommand hystrixCommand = batchCommandMethod.getAnnotation(HystrixCommand.class); + if (hystrixCommand == null) { + throw new IllegalStateException("batch method must be annotated with HystrixCommand annotation"); + } + // method of batch hystrix command must be passed to metaholder because basically collapser doesn't have any actions + // that should be invoked upon intercepted method, it's required only for underlying batch command + + MetaHolder.Builder builder = metaHolderBuilder(proxy, batchCommandMethod, obj, args, joinPoint); + + if (isCompileWeaving()) { + builder.ajcMethod(getAjcMethodAroundAdvice(obj.getClass(), batchCommandMethod.getName(), List.class)); + } + + builder.hystrixCollapser(hystrixCollapser); + builder.defaultCollapserKey(collapserMethod.getName()); + builder.collapserExecutionType(ExecutionType.getExecutionType(collapserReturnType)); + + builder.defaultCommandKey(batchCommandMethod.getName()); + builder.hystrixCommand(hystrixCommand); + builder.executionType(ExecutionType.getExecutionType(batchReturnType)); + builder.observable(observable); + FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(obj.getClass(), batchCommandMethod); + if (fallbackMethod.isPresent()) { + fallbackMethod.validateReturnType(batchCommandMethod); + builder + .fallbackMethod(fallbackMethod.getMethod()) + .fallbackExecutionType(ExecutionType.getExecutionType(fallbackMethod.getMethod().getReturnType())); + } + return builder.build(); + } + } + + private static class CommandMetaHolderFactory extends MetaHolderFactory { + @Override + public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) { + HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class); + ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType()); + MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint); + if (isCompileWeaving()) { + builder.ajcMethod(getAjcMethodFromTarget(joinPoint)); + } + return builder.defaultCommandKey(method.getName()) + .hystrixCommand(hystrixCommand) + .observableExecutionMode(hystrixCommand.observableExecutionMode()) + .executionType(executionType) + .observable(ExecutionType.OBSERVABLE == executionType) + .build(); + } + } + + private enum HystrixPointcutType { + COMMAND, + COLLAPSER; + + static HystrixPointcutType of(Method method) { + if (method.isAnnotationPresent(HystrixCommand.class)) { + return COMMAND; + } else if (method.isAnnotationPresent(HystrixCollapser.class)) { + return COLLAPSER; + } else { + String methodInfo = getMethodInfo(method); + throw new IllegalStateException("'https://github.com/Netflix/Hystrix/issues/1458' - no valid annotation found for: \n" + methodInfo); + } + } + } + + private static Method getAjcMethodFromTarget(JoinPoint joinPoint) { + return getAjcMethodAroundAdvice(joinPoint.getTarget().getClass(), (MethodSignature) joinPoint.getSignature()); + } + + + private static Class getFirstGenericParameter(Type type) { + return getFirstGenericParameter(type, 1); + } + + private static Class getFirstGenericParameter(final Type type, final int nestedDepth) { + int cDepth = 0; + Type tType = type; + + for (int cDept = 0; cDept < nestedDepth; cDept++) { + if (!(tType instanceof ParameterizedType)) + throw new IllegalStateException(String.format("Sub type at nesting level %d of %s is expected to be generic", cDepth, type)); + tType = ((ParameterizedType) tType).getActualTypeArguments()[cDept]; + } + + if (tType instanceof ParameterizedType) + return (Class) ((ParameterizedType) tType).getRawType(); + else if (tType instanceof Class) + return (Class) tType; + + throw new UnsupportedOperationException("Unsupported type " + tType); + } + + private static MetaHolder.Builder setDefaultProperties(MetaHolder.Builder builder, Class declaringClass, final ProceedingJoinPoint joinPoint) { + Optional defaultPropertiesOpt = AopUtils.getAnnotation(joinPoint, DefaultProperties.class); + builder.defaultGroupKey(declaringClass.getSimpleName()); + if (defaultPropertiesOpt.isPresent()) { + DefaultProperties defaultProperties = defaultPropertiesOpt.get(); + builder.defaultProperties(defaultProperties); + if (StringUtils.isNotBlank(defaultProperties.groupKey())) { + builder.defaultGroupKey(defaultProperties.groupKey()); + } + if (StringUtils.isNotBlank(defaultProperties.threadPoolKey())) { + builder.defaultThreadPoolKey(defaultProperties.threadPoolKey()); + } + } + return builder; + } + + private static MetaHolder.Builder setFallbackMethod(MetaHolder.Builder builder, Class declaringClass, Method commandMethod) { + FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(declaringClass, commandMethod); + if (fallbackMethod.isPresent()) { + fallbackMethod.validateReturnType(commandMethod); + builder + .fallbackMethod(fallbackMethod.getMethod()) + .fallbackExecutionType(ExecutionType.getExecutionType(fallbackMethod.getMethod().getReturnType())); + } + return builder; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/WeavingMode.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/WeavingMode.java new file mode 100644 index 0000000..0b04cf7 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/WeavingMode.java @@ -0,0 +1,25 @@ +/** + * 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.contrib.javanica.aop.aspectj; + +/** + * Created by dmgcodevil + */ +public enum WeavingMode { + + COMPILE, RUNTIME + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java new file mode 100644 index 0000000..1f06e5f --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java @@ -0,0 +1,173 @@ +/** + * 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.contrib.javanica.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.netflix.hystrix.contrib.javanica.command.ExecutionType; +import com.netflix.hystrix.contrib.javanica.command.MethodExecutionAction; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +/** + * Runtime information about an intercepted method invocation for a method + * annotated with {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult}, + * {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove} annotations. + * + * @author dmgcodevil + */ +public class CacheInvocationContext { + + private final Method method; + private final Object target; + private final MethodExecutionAction cacheKeyMethod; + private final ExecutionType executionType = ExecutionType.SYNCHRONOUS; + private final A cacheAnnotation; + + private List parameters = Collections.emptyList(); + private List keyParameters = Collections.emptyList(); + + /** + * Constructor to create CacheInvocationContext based on passed parameters. + * + * @param cacheAnnotation the caching annotation, like {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult} + * @param cacheKeyMethod the method to generate cache key + * @param target the current instance of intercepted method + * @param method the method annotated with on of caching annotations + * @param args the method arguments + */ + public CacheInvocationContext(A cacheAnnotation, MethodExecutionAction cacheKeyMethod, Object target, Method method, Object... args) { + this.method = method; + this.target = target; + this.cacheKeyMethod = cacheKeyMethod; + this.cacheAnnotation = cacheAnnotation; + Class[] parametersTypes = method.getParameterTypes(); + int parameterCount = parametersTypes.length; + if (parameterCount > 0) { + Annotation[][] parametersAnnotations = method.getParameterAnnotations(); + ImmutableList.Builder parametersBuilder = ImmutableList.builder(); + for (int pos = 0; pos < parameterCount; pos++) { + Class paramType = parametersTypes[pos]; + Object val = args[pos]; + parametersBuilder.add(new CacheInvocationParameter(paramType, val, parametersAnnotations[pos], pos)); + } + parameters = parametersBuilder.build(); + // get key parameters + Iterable filtered = Iterables.filter(parameters, new Predicate() { + @Override + public boolean apply(CacheInvocationParameter input) { + return input.hasCacheKeyAnnotation(); + } + }); + if (filtered.iterator().hasNext()) { + keyParameters = ImmutableList.builder().addAll(filtered).build(); + } else { + keyParameters = parameters; + } + } + } + + /** + * Gets intercepted method that annotated with caching annotation. + * + * @return method + */ + public Method getMethod() { + return method; + } + + /** + * Gets current instance that can be used to invoke {@link #cacheKeyMethod} or for another needs. + * + * @return current instance + */ + public Object getTarget() { + return target; + } + + public A getCacheAnnotation() { + return cacheAnnotation; + } + + /** + * Gets all method parameters. + * + * @return immutable list of {@link CacheInvocationParameter} objects + */ + public List getAllParameters() { + return parameters; + } + + /** + * Returns a clone of the array of all method parameters annotated with + * {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey} annotation to be used by the + * {@link HystrixCacheKeyGenerator} in creating a {@link HystrixGeneratedCacheKey}. The returned array + * may be the same as or a subset of the array returned by {@link #getAllParameters()}. + *

+ * Parameters in this array are selected by the following rules: + *

+ * + * @return immutable list of {@link CacheInvocationParameter} objects + */ + public List getKeyParameters() { + return keyParameters; + } + + /** + * Checks whether any method argument annotated with {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey} annotation. + * + * @return true if at least one method argument with {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey} annotation + */ + public boolean hasKeyParameters() { + return !keyParameters.isEmpty(); + } + + /** + * Gets method name to be used to get a key for request caching. + * + * @return method name + */ + public String getCacheKeyMethodName() { + return cacheKeyMethod != null ? cacheKeyMethod.getMethod().getName() : null; + } + + /** + * Gets action that invokes cache key method, the result of execution is used as cache key. + * + * @return cache key method execution action, see {@link MethodExecutionAction}. + */ + public MethodExecutionAction getCacheKeyMethod() { + return cacheKeyMethod; + } + + /** + * Gets execution type of cache key action. + * + * @return execution type + */ + public ExecutionType getExecutionType() { + return executionType; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java new file mode 100644 index 0000000..59d4d42 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java @@ -0,0 +1,88 @@ +/** + * 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.contrib.javanica.cache; + +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import com.netflix.hystrix.contrib.javanica.command.MethodExecutionAction; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCachingException; +import org.apache.commons.lang3.StringUtils; + +import java.lang.reflect.Method; + +import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getDeclaredMethod; + +/** + * Factory to create certain {@link CacheInvocationContext}. + * + * @author dmgcodevil + */ +public class CacheInvocationContextFactory { + + /** + * Create {@link CacheInvocationContext} parametrized with {@link CacheResult} annotation. + * + * @param metaHolder the meta holder, see {@link com.netflix.hystrix.contrib.javanica.command.MetaHolder} + * @return initialized and configured {@link CacheInvocationContext} + */ + public static CacheInvocationContext createCacheResultInvocationContext(MetaHolder metaHolder) { + Method method = metaHolder.getMethod(); + if (method.isAnnotationPresent(CacheResult.class)) { + CacheResult cacheResult = method.getAnnotation(CacheResult.class); + MethodExecutionAction cacheKeyMethod = createCacheKeyAction(cacheResult.cacheKeyMethod(), metaHolder); + return new CacheInvocationContext(cacheResult, cacheKeyMethod, metaHolder.getObj(), method, metaHolder.getArgs()); + } + return null; + } + + /** + * Create {@link CacheInvocationContext} parametrized with {@link CacheRemove} annotation. + * + * @param metaHolder the meta holder, see {@link com.netflix.hystrix.contrib.javanica.command.MetaHolder} + * @return initialized and configured {@link CacheInvocationContext} + */ + public static CacheInvocationContext createCacheRemoveInvocationContext(MetaHolder metaHolder) { + Method method = metaHolder.getMethod(); + if (method.isAnnotationPresent(CacheRemove.class)) { + CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class); + MethodExecutionAction cacheKeyMethod = createCacheKeyAction(cacheRemove.cacheKeyMethod(), metaHolder); + return new CacheInvocationContext(cacheRemove, cacheKeyMethod, metaHolder.getObj(), method, metaHolder.getArgs()); + } + return null; + } + + private static MethodExecutionAction createCacheKeyAction(String method, MetaHolder metaHolder) { + MethodExecutionAction cacheKeyAction = null; + if (StringUtils.isNotBlank(method)) { + Method cacheKeyMethod = getDeclaredMethod(metaHolder.getObj().getClass(), method, + metaHolder.getMethod().getParameterTypes()); + if (cacheKeyMethod == null) { + throw new HystrixCachingException("method with name '" + method + "' doesn't exist in class '" + + metaHolder.getObj().getClass() + "'"); + } + if (!cacheKeyMethod.getReturnType().equals(String.class)) { + throw new HystrixCachingException("return type of cacheKey method must be String. Method: '" + method + "', Class: '" + + metaHolder.getObj().getClass() + "'"); + } + + MetaHolder cMetaHolder = MetaHolder.builder().obj(metaHolder.getObj()).method(cacheKeyMethod).args(metaHolder.getArgs()).build(); + cacheKeyAction = new MethodExecutionAction(cMetaHolder.getObj(), cacheKeyMethod, cMetaHolder.getArgs(), cMetaHolder); + } + return cacheKeyAction; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java new file mode 100644 index 0000000..5ac2efe --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java @@ -0,0 +1,112 @@ +/** + * 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.contrib.javanica.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Set; + +/** + * A parameter to an intercepted method invocation. Contains the parameter value + * as well static type and annotation information about the parameter. + * + * @author dmgcodevil + */ +public class CacheInvocationParameter { + + private final Class rawType; + private final Object value; + private final CacheKey cacheKeyAnnotation; + private final Set annotations; + private final int position; + + public CacheInvocationParameter(Class rawType, Object value, Annotation[] annotations, int position) { + this.rawType = rawType; + this.value = value; + this.annotations = ImmutableSet.builder().addAll(Arrays.asList(annotations)).build(); + this.position = position; + this.cacheKeyAnnotation = (CacheKey) cacheKeyAnnotation(); + } + + /** + * Returns an immutable Set of all Annotations on this method parameter, never null. + * + * @return set of {@link Annotation} + */ + public Set getAnnotations() { + return annotations; + } + + /** + * Gets {@link CacheKey} for the parameter. + * + * @return {@link CacheKey} annotation or null if the parameter isn't annotated with {@link CacheKey}. + */ + public CacheKey getCacheKeyAnnotation() { + return cacheKeyAnnotation; + } + + /** + * Checks whether the parameter annotated with {@link CacheKey} or not. + * + * @return true if parameter annotated with {@link CacheKey} otherwise - false + */ + public boolean hasCacheKeyAnnotation() { + return cacheKeyAnnotation != null; + } + + /** + * Gets the parameter type as declared on the method. + * + * @return parameter type + */ + public Class getRawType() { + return rawType; + } + + /** + * Gets the parameter value + * + * @return parameter value + */ + public Object getValue() { + return value; + } + + /** + * Gets index of the parameter in the original parameter array. + * + * @return index of the parameter + */ + public int getPosition() { + return position; + } + + private Annotation cacheKeyAnnotation() { + return Iterables.tryFind(annotations, new Predicate() { + @Override + public boolean apply(Annotation input) { + return input.annotationType().equals(CacheKey.class); + } + }).orNull(); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultHystrixGeneratedCacheKey.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultHystrixGeneratedCacheKey.java new file mode 100644 index 0000000..e694b71 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultHystrixGeneratedCacheKey.java @@ -0,0 +1,59 @@ +/** + * Copyright 2012 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.contrib.javanica.cache; + +import com.google.common.base.Objects; + +/** + * Default implementation of {@link HystrixGeneratedCacheKey}. + * + * @author dmgcodevil + */ +public class DefaultHystrixGeneratedCacheKey implements HystrixGeneratedCacheKey { + + /** + * Means "do not cache". + */ + public static final DefaultHystrixGeneratedCacheKey EMPTY = new DefaultHystrixGeneratedCacheKey(null); + + private String cacheKey; + + public DefaultHystrixGeneratedCacheKey(String cacheKey) { + this.cacheKey = cacheKey; + } + + @Override + public String getCacheKey() { + return cacheKey; + } + + @Override + public int hashCode() { + return Objects.hashCode(cacheKey); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + DefaultHystrixGeneratedCacheKey that = (DefaultHystrixGeneratedCacheKey) o; + + return Objects.equal(this.cacheKey, that.cacheKey); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java new file mode 100644 index 0000000..026b661 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java @@ -0,0 +1,98 @@ +/** + * 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.contrib.javanica.cache; + + +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.command.MethodExecutionAction; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCacheKeyGenerationException; +import org.apache.commons.lang3.StringUtils; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.List; + +/** + * Generates a {@link HystrixGeneratedCacheKey} based on + * a {@link CacheInvocationContext}. + *

+ * Implementation is thread-safe. + * + * @author dmgcodevil + */ +public class HystrixCacheKeyGenerator { + + private static final HystrixCacheKeyGenerator INSTANCE = new HystrixCacheKeyGenerator(); + + public static HystrixCacheKeyGenerator getInstance() { + return INSTANCE; + } + + public HystrixGeneratedCacheKey generateCacheKey(CacheInvocationContext cacheInvocationContext) throws HystrixCacheKeyGenerationException { + MethodExecutionAction cacheKeyMethod = cacheInvocationContext.getCacheKeyMethod(); + if (cacheKeyMethod != null) { + try { + return new DefaultHystrixGeneratedCacheKey((String) cacheKeyMethod.execute(cacheInvocationContext.getExecutionType())); + } catch (Throwable throwable) { + throw new HystrixCacheKeyGenerationException(throwable); + } + } else { + if (cacheInvocationContext.hasKeyParameters()) { + StringBuilder cacheKeyBuilder = new StringBuilder(); + for (CacheInvocationParameter parameter : cacheInvocationContext.getKeyParameters()) { + CacheKey cacheKey = parameter.getCacheKeyAnnotation(); + if (cacheKey != null && StringUtils.isNotBlank(cacheKey.value())) { + appendPropertyValue(cacheKeyBuilder, Arrays.asList(StringUtils.split(cacheKey.value(), ".")), parameter.getValue()); + } else { + cacheKeyBuilder.append(parameter.getValue()); + } + } + return new DefaultHystrixGeneratedCacheKey(cacheKeyBuilder.toString()); + } else { + return DefaultHystrixGeneratedCacheKey.EMPTY; + } + } + } + + private Object appendPropertyValue(StringBuilder cacheKeyBuilder, List names, Object obj) throws HystrixCacheKeyGenerationException { + for (String name : names) { + if (obj != null) { + obj = getPropertyValue(name, obj); + } + } + if (obj != null) { + cacheKeyBuilder.append(obj); + } + return obj; + } + + private Object getPropertyValue(String name, Object obj) throws HystrixCacheKeyGenerationException { + try { + return new PropertyDescriptor(name, obj.getClass()) + .getReadMethod().invoke(obj); + } catch (IllegalAccessException e) { + throw new HystrixCacheKeyGenerationException(e); + } catch (IntrospectionException e) { + throw new HystrixCacheKeyGenerationException(e); + } catch (InvocationTargetException e) { + throw new HystrixCacheKeyGenerationException(e); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java new file mode 100644 index 0000000..101e443 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java @@ -0,0 +1,37 @@ +/** + * 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.contrib.javanica.cache; + +/** + * Specific interface to adopt {@link HystrixGeneratedCacheKey} for Hystrix environment. + * + * @author dmgcodevil + */ +public interface HystrixGeneratedCacheKey { + + /** + * Key to be used for request caching. + *

+ * By default this returns null which means "do not cache". + *

+ * To enable caching override this method and return a string key uniquely representing the state of a command instance. + *

+ * If multiple command instances in the same request scope match keys then only the first will be executed and all others returned from cache. + * + * @return cacheKey + */ + String getCacheKey(); +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java new file mode 100644 index 0000000..db9c356 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java @@ -0,0 +1,55 @@ +/** + * 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.contrib.javanica.cache; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixRequestCache; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault; + + +/** + * Cache manager to work with {@link HystrixRequestCache}. + * + * @author dmgcodevil + */ +public final class HystrixRequestCacheManager { + + private static final HystrixRequestCacheManager INSTANCE = new HystrixRequestCacheManager(); + + private HystrixRequestCacheManager() { + } + + public static HystrixRequestCacheManager getInstance() { + return INSTANCE; + } + + /** + * Clears the cache for a given cacheKey context. + * + * @param context the runtime information about an intercepted method invocation for a method + * annotated with {@link CacheRemove} annotation + */ + public void clearCache(CacheInvocationContext context) { + HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); + String cacheName = context.getCacheAnnotation().commandKey(); + HystrixGeneratedCacheKey hystrixGeneratedCacheKey = + defaultCacheKeyGenerator.generateCacheKey(context); + String key = hystrixGeneratedCacheKey.getCacheKey(); + HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey(cacheName), + HystrixConcurrencyStrategyDefault.getInstance()).clear(key); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java new file mode 100644 index 0000000..2f9d1ba --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java @@ -0,0 +1,47 @@ +/** + * 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.contrib.javanica.cache.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method argument as part of the cache key. + * If no arguments are marked all arguments are used. + * If {@link CacheResult} or {@link CacheRemove} annotation has specified cacheKeyMethod then + * a method arguments will not be used to build cache key even if they annotated with {@link CacheKey}. + * + * @author dmgcodevil + */ +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CacheKey { + + /** + * Allows specify name of a certain argument property. + * for example: @CacheKey("id") User user, + * or in case composite property: @CacheKey("profile.name") User user. + * null properties are ignored, i.e. if profile is null + * then result of @CacheKey("profile.name") User user will be empty string. + * + * @return name of an argument property + */ + String value() default ""; +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java new file mode 100644 index 0000000..aef4459 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java @@ -0,0 +1,55 @@ +/** + * 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.contrib.javanica.cache.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks methods used to invalidate cache of a command. + * Generated cache key must be same as key generated within {@link CacheResult} context. + * + * @author dmgcodevil + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface CacheRemove { + + /** + * Command name is used to find appropriate Hystrix command that cache should be cleared. + * + * @return command name + */ + String commandKey(); + + /** + * Method name to be used to get a key for request caching. + * The command and cache key method should be placed in the same class and have same method signature except + * cache key method return type, that should be String. + *

+ * cacheKeyMethod has higher priority than an arguments of a method, that means what actual arguments + * of a method that annotated with {@link CacheResult} will not be used to generate cache key, instead specified + * cacheKeyMethod fully assigns to itself responsibility for cache key generation. + * By default this returns empty string which means "do not use cache method". + * + * @return method name or empty string + */ + String cacheKeyMethod() default ""; +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java new file mode 100644 index 0000000..dfa52f3 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java @@ -0,0 +1,48 @@ +/** + * 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.contrib.javanica.cache.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a methods that results should be cached for a Hystrix command. + * This annotation must be used in conjunction with {@link com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand} annotation. + * + * @author dmgcodevil + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CacheResult { + + /** + * Method name to be used to get a key for request caching. + * The command and cache key method should be placed in the same class and have same method signature except + * cache key method return type, that should be String. + *

+ * cacheKeyMethod has higher priority than an arguments of a method, that means what actual arguments + * of a method that annotated with {@link CacheResult} will not be used to generate cache key, instead specified + * cacheKeyMethod fully assigns to itself responsibility for cache key generation. + * By default this returns empty string which means "do not use cache method". + * + * @return method name or empty string + */ + String cacheKeyMethod() default ""; +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CommandCollapser.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CommandCollapser.java new file mode 100644 index 0000000..4e3ab1a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CommandCollapser.java @@ -0,0 +1,90 @@ +/** + * Copyright 2012 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.contrib.javanica.collapser; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.command.BatchHystrixCommand; +import com.netflix.hystrix.contrib.javanica.command.HystrixCommandBuilderFactory; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; + +import java.util.Collection; +import java.util.List; + +import static org.slf4j.helpers.MessageFormatter.arrayFormat; + +/** + * Collapses multiple requests into a single {@link HystrixCommand} execution based + * on a time window and optionally a max batch size. + */ +public class CommandCollapser extends HystrixCollapser, Object, Object> { + + private MetaHolder metaHolder; + + private static final String ERROR_MSG = "Failed to map all collapsed requests to response. " + + "The expected contract has not been respected. "; + + private static final String ERROR_MSF_TEMPLATE = "Collapser key: '{}', requests size: '{}', response size: '{}'"; + + /** + * Constructor with parameters. + * + * @param metaHolder the {@link MetaHolder} + */ + public CommandCollapser(MetaHolder metaHolder) { + super(HystrixCommandBuilderFactory.getInstance().create(metaHolder).getSetterBuilder().buildCollapserCommandSetter()); + this.metaHolder = metaHolder; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getRequestArgument() { + return metaHolder.getArgs(); + } + + /** + * Creates batch command. + */ + @Override + protected HystrixCommand> createCommand( + Collection> collapsedRequests) { + return new BatchHystrixCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder, collapsedRequests)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void mapResponseToRequests(List batchResponse, + Collection> collapsedRequests) { + if (batchResponse.size() < collapsedRequests.size()) { + throw new RuntimeException(createMessage(collapsedRequests, batchResponse)); + } + int count = 0; + for (CollapsedRequest request : collapsedRequests) { + request.setResponse(batchResponse.get(count++)); + } + } + + private String createMessage(Collection> requests, + List response) { + return ERROR_MSG + arrayFormat(ERROR_MSF_TEMPLATE, new Object[]{getCollapserKey().name(), + requests.size(), response.size()}).getMessage(); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java new file mode 100644 index 0000000..6619635 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java @@ -0,0 +1,233 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; +import com.netflix.hystrix.contrib.javanica.cache.HystrixCacheKeyGenerator; +import com.netflix.hystrix.contrib.javanica.cache.HystrixGeneratedCacheKey; +import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; + +import javax.annotation.concurrent.ThreadSafe; +import java.util.Collection; +import java.util.List; + +/** + * Base class for hystrix commands. + * + * @param the return type + */ +@ThreadSafe +public abstract class AbstractHystrixCommand extends com.netflix.hystrix.HystrixCommand { + + private final CommandActions commandActions; + private final CacheInvocationContext cacheResultInvocationContext; + private final CacheInvocationContext cacheRemoveInvocationContext; + private final Collection> collapsedRequests; + private final List> ignoreExceptions; + private final ExecutionType executionType; + private final HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); + + protected AbstractHystrixCommand(HystrixCommandBuilder builder) { + super(builder.getSetterBuilder().build()); + this.commandActions = builder.getCommandActions(); + this.collapsedRequests = builder.getCollapsedRequests(); + this.cacheResultInvocationContext = builder.getCacheResultInvocationContext(); + this.cacheRemoveInvocationContext = builder.getCacheRemoveInvocationContext(); + this.ignoreExceptions = builder.getIgnoreExceptions(); + this.executionType = builder.getExecutionType(); + } + + /** + * Gets command action. + * + * @return command action + */ + protected CommandAction getCommandAction() { + return commandActions.getCommandAction(); + } + + /** + * Gets fallback action. + * + * @return fallback action + */ + protected CommandAction getFallbackAction() { + return commandActions.getFallbackAction(); + } + + /** + * Gets collapsed requests. + * + * @return collapsed requests + */ + protected Collection> getCollapsedRequests() { + return collapsedRequests; + } + + /** + * Gets exceptions types which should be ignored. + * + * @return exceptions types + */ + protected List> getIgnoreExceptions() { + return ignoreExceptions; + } + + protected ExecutionType getExecutionType() { + return executionType; + } + + /** + * {@inheritDoc}. + */ + @Override + protected String getCacheKey() { + String key = null; + if (cacheResultInvocationContext != null) { + HystrixGeneratedCacheKey hystrixGeneratedCacheKey = + defaultCacheKeyGenerator.generateCacheKey(cacheResultInvocationContext); + key = hystrixGeneratedCacheKey.getCacheKey(); + } + return key; + } + + /** + * Check whether triggered exception is ignorable. + * + * @param throwable the exception occurred during a command execution + * @return true if exception is ignorable, otherwise - false + */ + boolean isIgnorable(Throwable throwable) { + if (ignoreExceptions == null || ignoreExceptions.isEmpty()) { + return false; + } + for (Class ignoreException : ignoreExceptions) { + if (ignoreException.isAssignableFrom(throwable.getClass())) { + return true; + } + } + return false; + } + + /** + * Executes an action. If an action has failed and an exception is ignorable then propagate it as HystrixBadRequestException + * otherwise propagate original exception to trigger fallback method. + * Note: If an exception occurred in a command directly extends {@link java.lang.Throwable} then this exception cannot be re-thrown + * as original exception because HystrixCommand.run() allows throw subclasses of {@link java.lang.Exception}. + * Thus we need to wrap cause in RuntimeException, anyway in this case the fallback logic will be triggered. + * + * @param action the action + * @return result of command action execution + */ + Object process(Action action) throws Exception { + Object result; + try { + result = action.execute(); + flushCache(); + } catch (CommandActionExecutionException throwable) { + Throwable cause = throwable.getCause(); + if (isIgnorable(cause)) { + throw new HystrixBadRequestException(cause.getMessage(), cause); + } + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Exception) { + throw (Exception) cause; + } else { + // instance of Throwable + throw new CommandActionExecutionException(cause); + } + } + return result; + } + + /** + * {@inheritDoc}. + */ + @Override + protected abstract T run() throws Exception; + + /** + * Clears cache for the specified hystrix command. + */ + protected void flushCache() { + if (cacheRemoveInvocationContext != null) { + HystrixRequestCacheManager.getInstance().clearCache(cacheRemoveInvocationContext); + } + } + + /** + * Common action. + */ + abstract class Action { + /** + * Each implementation of this method should wrap any exceptions in CommandActionExecutionException. + * + * @return execution result + * @throws CommandActionExecutionException + */ + abstract Object execute() throws CommandActionExecutionException; + } + + + /** + * Builder to create error message for failed fallback operation. + */ + static class FallbackErrorMessageBuilder { + private StringBuilder builder = new StringBuilder("failed to process fallback"); + + static FallbackErrorMessageBuilder create() { + return new FallbackErrorMessageBuilder(); + } + + public FallbackErrorMessageBuilder append(CommandAction action, Throwable throwable) { + return commandAction(action).exception(throwable); + } + + private FallbackErrorMessageBuilder commandAction(CommandAction action) { + if (action instanceof CommandExecutionAction || action instanceof LazyCommandExecutionAction) { + builder.append(": '").append(action.getActionName()).append("'. ") + .append(action.getActionName()).append(" fallback is a hystrix command. "); + } else if (action instanceof MethodExecutionAction) { + builder.append(" is the method: '").append(action.getActionName()).append("'. "); + } + return this; + } + + private FallbackErrorMessageBuilder exception(Throwable throwable) { + if (throwable instanceof HystrixBadRequestException) { + builder.append("exception: '").append(throwable.getCause().getClass()) + .append("' occurred in fallback was ignored and wrapped to HystrixBadRequestException.\n"); + } else if (throwable instanceof HystrixRuntimeException) { + builder.append("exception: '").append(throwable.getCause().getClass()) + .append("' occurred in fallback wasn't ignored.\n"); + } + return this; + } + + public String build() { + return builder.toString(); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncResult.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncResult.java new file mode 100644 index 0000000..9354f70 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncResult.java @@ -0,0 +1,63 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Fake implementation of {@link Future}. Can be used for method signatures + * which are declared with a Future return type for asynchronous execution. + * Provides abstract {@link #invoke()} method to wrap some logic for an asynchronous call. + * + * @param the type of result + */ +public abstract class AsyncResult implements Future, ClosureCommand { + + private static final String ERROR_MSG = "AsyncResult is just a stub and cannot be used as complete implementation of Future"; + + @Override + public boolean cancel(boolean mayInterruptIfRunning) throws UnsupportedOperationException { + throw new UnsupportedOperationException(ERROR_MSG); + } + + @Override + public boolean isCancelled() throws UnsupportedOperationException { + throw new UnsupportedOperationException(ERROR_MSG); + } + + @Override + public boolean isDone() throws UnsupportedOperationException { + throw new UnsupportedOperationException(ERROR_MSG); + } + + @Override + public T get() throws UnsupportedOperationException { + throw new UnsupportedOperationException(ERROR_MSG); + } + + @Override + public T get(long timeout, TimeUnit unit) throws UnsupportedOperationException { + throw new UnsupportedOperationException(ERROR_MSG); + } + + /** + * {@inheritDoc}. + */ + @Override + public abstract T invoke(); + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java new file mode 100644 index 0000000..702b607 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java @@ -0,0 +1,104 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.concurrent.ThreadSafe; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static com.netflix.hystrix.contrib.javanica.exception.ExceptionUtils.unwrapCause; +import static com.netflix.hystrix.contrib.javanica.utils.CommonUtils.createArgsForFallback; + +/** + * This command is used in collapser. + */ +@ThreadSafe +public class BatchHystrixCommand extends AbstractHystrixCommand> { + + private static final Logger LOGGER = LoggerFactory.getLogger(BatchHystrixCommand.class); + + public BatchHystrixCommand(HystrixCommandBuilder builder) { + super(builder); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + protected List run() throws Exception { + Object[] args = toArgs(getCollapsedRequests()); + return (List) process(args); + } + + private Object process(final Object[] args) throws Exception { + return process(new Action() { + @Override + Object execute() { + return getCommandAction().executeWithArgs(getExecutionType(), args); + } + }); + } + + + @Override + @SuppressWarnings("unchecked") + protected List getFallback() { + if (getFallbackAction() != null) { + final CommandAction commandAction = getFallbackAction(); + + try { + return (List) process(new Action() { + @Override + Object execute() { + MetaHolder metaHolder = commandAction.getMetaHolder(); + Object[] args = toArgs(getCollapsedRequests()); + args = createArgsForFallback(args, metaHolder, getExecutionException()); + return commandAction.executeWithArgs(commandAction.getMetaHolder().getFallbackExecutionType(), args); + } + }); + } catch (Throwable e) { + LOGGER.error(FallbackErrorMessageBuilder.create() + .append(commandAction, e).build()); + throw new FallbackInvocationException(unwrapCause(e)); + } + } else { + return super.getFallback(); + } + + } + + private Object[] toArgs(Collection> requests) { + return new Object[]{collect(requests)}; + } + + private List collect(Collection> requests) { + List commandArgs = new ArrayList(); + for (HystrixCollapser.CollapsedRequest request : requests) { + final Object[] args = (Object[]) request.getArgument(); + commandArgs.add(args[0]); + } + return commandArgs; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ClosureCommand.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ClosureCommand.java new file mode 100644 index 0000000..3b53109 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ClosureCommand.java @@ -0,0 +1,31 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +/** + * This interface is used to perform command logic within an anonymous class and basically used as wrapper for some logic. + * + * @param command result type + */ +public interface ClosureCommand { + + /** + * Process logic. + * + * @return result + */ + T invoke(); +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandAction.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandAction.java new file mode 100644 index 0000000..06c34dd --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandAction.java @@ -0,0 +1,53 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; + +/** + * Simple action to encapsulate some logic to process it in a Hystrix command. + */ +public interface CommandAction { + + MetaHolder getMetaHolder(); + + /** + * Executes action in accordance with the given execution type. + * + * @param executionType the execution type + * @return result of execution + * @throws com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException + */ + Object execute(ExecutionType executionType) throws CommandActionExecutionException; + + /** + * Executes action with parameters in accordance with the given execution ty + * + * @param executionType the execution type + * @param args the parameters of the action + * @return result of execution + * @throws com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException + */ + Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException; + + /** + * Gets action name. Useful for debugging. + * + * @return the action name + */ + String getActionName(); + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java new file mode 100644 index 0000000..6686632 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java @@ -0,0 +1,68 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +/** + * Wrapper for command actions combines different actions together. + * + * @author dmgcodevil + */ +public class CommandActions { + + private final CommandAction commandAction; + private final CommandAction fallbackAction; + + public CommandActions(Builder builder) { + this.commandAction = builder.commandAction; + this.fallbackAction = builder.fallbackAction; + } + + public static Builder builder() { + return new Builder(); + } + + public CommandAction getCommandAction() { + return commandAction; + } + + public CommandAction getFallbackAction() { + return fallbackAction; + } + + public boolean hasFallbackAction() { + return fallbackAction != null; + } + + public static class Builder { + private CommandAction commandAction; + private CommandAction fallbackAction; + + public Builder commandAction(CommandAction pCommandAction) { + this.commandAction = pCommandAction; + return this; + } + + public Builder fallbackAction(CommandAction pFallbackAction) { + this.fallbackAction = pFallbackAction; + return this; + } + + public CommandActions build() { + return new CommandActions(this); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutionAction.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutionAction.java new file mode 100644 index 0000000..0cc55c8 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutionAction.java @@ -0,0 +1,67 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; + +/** + * Action to execute a Hystrix command. + */ +public class CommandExecutionAction implements CommandAction { + + private HystrixInvokable hystrixCommand; + private MetaHolder metaHolder; + + /** + * Constructor with parameters. + * + * @param hystrixCommand the hystrix command to execute. + */ + public CommandExecutionAction(HystrixInvokable hystrixCommand, MetaHolder metaHolder) { + this.hystrixCommand = hystrixCommand; + this.metaHolder = metaHolder; + } + + @Override + public MetaHolder getMetaHolder() { + return metaHolder; + } + + @Override + public Object execute(ExecutionType executionType) throws CommandActionExecutionException { + return CommandExecutor.execute(hystrixCommand, executionType, metaHolder); + } + + @Override + public Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException { + return CommandExecutor.execute(hystrixCommand, executionType, metaHolder); + } + + /** + * {@inheritDoc} + */ + @Override + public String getActionName() { + if (hystrixCommand != null && hystrixCommand instanceof HystrixInvokableInfo) { + HystrixInvokableInfo info = (HystrixInvokableInfo) hystrixCommand; + return info.getCommandKey().name(); + } + return ""; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutor.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutor.java new file mode 100644 index 0000000..9c8bc8a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutor.java @@ -0,0 +1,85 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import com.netflix.hystrix.HystrixExecutable; +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.HystrixObservable; +import com.netflix.hystrix.contrib.javanica.annotation.ObservableExecutionMode; +import com.netflix.hystrix.contrib.javanica.utils.FutureDecorator; +import org.apache.commons.lang3.Validate; + +/** + * Invokes necessary method of {@link HystrixExecutable} or {@link HystrixObservable} for specified execution type: + *

+ * {@link ExecutionType#SYNCHRONOUS} -> {@link com.netflix.hystrix.HystrixExecutable#execute()} + *

+ * {@link ExecutionType#ASYNCHRONOUS} -> {@link com.netflix.hystrix.HystrixExecutable#queue()} + *

+ * {@link ExecutionType#OBSERVABLE} -> depends on specify observable execution mode: + * {@link ObservableExecutionMode#EAGER} - {@link HystrixObservable#observe()}, + * {@link ObservableExecutionMode#LAZY} - {@link HystrixObservable#toObservable()}. + */ +public class CommandExecutor { + + /** + * Calls a method of {@link HystrixExecutable} in accordance with specified execution type. + * + * @param invokable {@link HystrixInvokable} + * @param metaHolder {@link MetaHolder} + * @return the result of invocation of specific method. + * @throws RuntimeException + */ + public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException { + Validate.notNull(invokable); + Validate.notNull(metaHolder); + + switch (executionType) { + case SYNCHRONOUS: { + return castToExecutable(invokable, executionType).execute(); + } + case ASYNCHRONOUS: { + HystrixExecutable executable = castToExecutable(invokable, executionType); + if (metaHolder.hasFallbackMethodCommand() + && ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()) { + return new FutureDecorator(executable.queue()); + } + return executable.queue(); + } + case OBSERVABLE: { + HystrixObservable observable = castToObservable(invokable); + return ObservableExecutionMode.EAGER == metaHolder.getObservableExecutionMode() ? observable.observe() : observable.toObservable(); + } + default: + throw new RuntimeException("unsupported execution type: " + executionType); + } + } + + private static HystrixExecutable castToExecutable(HystrixInvokable invokable, ExecutionType executionType) { + if (invokable instanceof HystrixExecutable) { + return (HystrixExecutable) invokable; + } + throw new RuntimeException("Command should implement " + HystrixExecutable.class.getCanonicalName() + " interface to execute in: " + executionType + " mode"); + } + + private static HystrixObservable castToObservable(HystrixInvokable invokable) { + if (invokable instanceof HystrixObservable) { + return (HystrixObservable) invokable; + } + throw new RuntimeException("Command should implement " + HystrixObservable.class.getCanonicalName() + " interface to execute in observable mode"); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ExecutionType.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ExecutionType.java new file mode 100644 index 0000000..55f4f26 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ExecutionType.java @@ -0,0 +1,72 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import com.google.common.collect.ImmutableSet; +import rx.Completable; +import rx.Observable; +import rx.Single; + +import java.util.Set; +import java.util.concurrent.Future; + +/** + * Specifies executions types. + */ +public enum ExecutionType { + + /** + * Used for asynchronous execution of command. + */ + ASYNCHRONOUS, + + /** + * Used for synchronous execution of command. + */ + SYNCHRONOUS, + + /** + * Reactive execution (asynchronous callback). + */ + OBSERVABLE; + + // RX types + private static final Set RX_TYPES = ImmutableSet.of(Observable.class, Single.class, Completable.class); + + /** + * Gets execution type for specified class type. + * @param type the type + * @return the execution type {@link ExecutionType} + */ + public static ExecutionType getExecutionType(Class type) { + if (Future.class.isAssignableFrom(type)) { + return ExecutionType.ASYNCHRONOUS; + } else if (isRxType(type)) { + return ExecutionType.OBSERVABLE; + } else { + return ExecutionType.SYNCHRONOUS; + } + } + + private static boolean isRxType(Class cl) { + for (Class rxType : RX_TYPES) { + if (rxType.isAssignableFrom(cl)) { + return true; + } + } + return false; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java new file mode 100644 index 0000000..fbe8a25 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java @@ -0,0 +1,89 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.concurrent.ThreadSafe; + +import static com.netflix.hystrix.contrib.javanica.exception.ExceptionUtils.unwrapCause; +import static com.netflix.hystrix.contrib.javanica.utils.CommonUtils.createArgsForFallback; + +/** + * Implementation of AbstractHystrixCommand which returns an Object as result. + */ +@ThreadSafe +public class GenericCommand extends AbstractHystrixCommand { + + private static final Logger LOGGER = LoggerFactory.getLogger(GenericCommand.class); + + public GenericCommand(HystrixCommandBuilder builder) { + super(builder); + } + + /** + * {@inheritDoc} + */ + @Override + protected Object run() throws Exception { + LOGGER.debug("execute command: {}", getCommandKey().name()); + return process(new Action() { + @Override + Object execute() { + return getCommandAction().execute(getExecutionType()); + } + }); + } + + /** + * The fallback is performed whenever a command execution fails. + * Also a fallback method will be invoked within separate command in the case if fallback method was annotated with + * HystrixCommand annotation, otherwise current implementation throws RuntimeException and leaves the caller to deal with it + * (see {@link super#getFallback()}). + * The getFallback() is always processed synchronously. + * Since getFallback() can throw only runtime exceptions thus any exceptions are thrown within getFallback() method + * are wrapped in {@link FallbackInvocationException}. + * A caller gets {@link com.netflix.hystrix.exception.HystrixRuntimeException} + * and should call getCause to get original exception that was thrown in getFallback(). + * + * @return result of invocation of fallback method or RuntimeException + */ + @Override + protected Object getFallback() { + final CommandAction commandAction = getFallbackAction(); + if (commandAction != null) { + try { + return process(new Action() { + @Override + Object execute() { + MetaHolder metaHolder = commandAction.getMetaHolder(); + Object[] args = createArgsForFallback(metaHolder, getExecutionException()); + return commandAction.executeWithArgs(metaHolder.getFallbackExecutionType(), args); + } + }); + } catch (Throwable e) { + LOGGER.error(FallbackErrorMessageBuilder.create() + .append(commandAction, e).build()); + throw new FallbackInvocationException(unwrapCause(e)); + } + } else { + return super.getFallback(); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java new file mode 100644 index 0000000..75f56a5 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java @@ -0,0 +1,179 @@ +/** + * 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.contrib.javanica.command; + + +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; +import com.netflix.hystrix.contrib.javanica.cache.HystrixCacheKeyGenerator; +import com.netflix.hystrix.contrib.javanica.cache.HystrixGeneratedCacheKey; +import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; +import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Completable; +import rx.Observable; +import rx.Single; +import rx.functions.Func1; + +import javax.annotation.concurrent.ThreadSafe; +import java.util.List; + +import static com.netflix.hystrix.contrib.javanica.utils.CommonUtils.createArgsForFallback; + +/** + * Generic class for all observable commands executed within javanica context. + */ +@ThreadSafe +public class GenericObservableCommand extends HystrixObservableCommand { + + private final CommandActions commandActions; + private final CacheInvocationContext cacheResultInvocationContext; + private final CacheInvocationContext cacheRemoveInvocationContext; + private final List> ignoreExceptions; + private final ExecutionType executionType; + private final HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); + + private static final Logger LOGGER = LoggerFactory.getLogger(GenericObservableCommand.class); + + public GenericObservableCommand(HystrixCommandBuilder builder) { + super(builder.getSetterBuilder().buildObservableCommandSetter()); + this.commandActions = builder.getCommandActions(); + this.cacheResultInvocationContext = builder.getCacheResultInvocationContext(); + this.cacheRemoveInvocationContext = builder.getCacheRemoveInvocationContext(); + this.ignoreExceptions = builder.getIgnoreExceptions(); + this.executionType = builder.getExecutionType(); + } + + /** + *{@inheritDoc}. + */ + @Override + protected Observable construct() { + Observable result; + try { + Observable observable = toObservable(commandActions.getCommandAction().execute(executionType)); + result = observable + .onErrorResumeNext(new Func1() { + @Override + public Observable call(Throwable throwable) { + if (isIgnorable(throwable)) { + return Observable.error(new HystrixBadRequestException(throwable.getMessage(), throwable)); + } + return Observable.error(throwable); + } + }); + flushCache(); + } catch (CommandActionExecutionException throwable) { + Throwable cause = throwable.getCause(); + if (isIgnorable(cause)) { + throw new HystrixBadRequestException(cause.getMessage(), cause); + } + throw throwable; + } + return result; + } + + /** + *{@inheritDoc}. + */ + @Override + protected Observable resumeWithFallback() { + if (commandActions.hasFallbackAction()) { + MetaHolder metaHolder = commandActions.getFallbackAction().getMetaHolder(); + Throwable cause = getExecutionException(); + if (cause instanceof CommandActionExecutionException) { + cause = cause.getCause(); + } + + Object[] args = createArgsForFallback(metaHolder, cause); + try { + Object res = commandActions.getFallbackAction().executeWithArgs(executionType, args); + if (res instanceof Observable) { + return (Observable) res; + } else if (res instanceof Single) { + return ((Single) res).toObservable(); + } else if (res instanceof Completable) { + return ((Completable) res).toObservable(); + } else { + return Observable.just(res); + } + } catch (Exception e) { + LOGGER.error(AbstractHystrixCommand.FallbackErrorMessageBuilder.create() + .append(commandActions.getFallbackAction(), e).build()); + throw new FallbackInvocationException(e.getCause()); + } + } + return super.resumeWithFallback(); + } + + /** + * {@inheritDoc}. + */ + @Override + protected String getCacheKey() { + String key = null; + if (cacheResultInvocationContext != null) { + HystrixGeneratedCacheKey hystrixGeneratedCacheKey = + defaultCacheKeyGenerator.generateCacheKey(cacheResultInvocationContext); + key = hystrixGeneratedCacheKey.getCacheKey(); + } + return key; + } + + /** + * Clears cache for the specified hystrix command. + */ + protected void flushCache() { + if (cacheRemoveInvocationContext != null) { + HystrixRequestCacheManager.getInstance().clearCache(cacheRemoveInvocationContext); + } + } + + /** + * Check whether triggered exception is ignorable. + * + * @param throwable the exception occurred during a command execution + * @return true if exception is ignorable, otherwise - false + */ + boolean isIgnorable(Throwable throwable) { + if (ignoreExceptions == null || ignoreExceptions.isEmpty()) { + return false; + } + for (Class ignoreException : ignoreExceptions) { + if (ignoreException.isAssignableFrom(throwable.getClass())) { + return true; + } + } + return false; + } + + private Observable toObservable(Object obj) { + if (Observable.class.isAssignableFrom(obj.getClass())) { + return (Observable) obj; + } else if (Completable.class.isAssignableFrom(obj.getClass())) { + return ((Completable) obj).toObservable(); + } else if (Single.class.isAssignableFrom(obj.getClass())) { + return ((Single) obj).toObservable(); + } else { + throw new IllegalStateException("unsupported rx type: " + obj.getClass()); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericSetterBuilder.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericSetterBuilder.java new file mode 100644 index 0000000..494415b --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericSetterBuilder.java @@ -0,0 +1,188 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; +import com.netflix.hystrix.contrib.javanica.exception.HystrixPropertyException; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.concurrent.Immutable; +import java.util.Collections; +import java.util.List; + +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.initializeCollapserProperties; + +/** + * Builder for Hystrix Setters: {@link HystrixCommand.Setter}, {@link HystrixObservableCommand.Setter}, {@link HystrixCollapser.Setter}. + */ +@Immutable +public class GenericSetterBuilder { + + private String groupKey; + private String commandKey; + private String threadPoolKey; + private String collapserKey; + private HystrixCollapser.Scope scope; + private List commandProperties = Collections.emptyList(); + private List collapserProperties = Collections.emptyList(); + private List threadPoolProperties = Collections.emptyList(); + + public GenericSetterBuilder(Builder builder) { + this.groupKey = builder.groupKey; + this.commandKey = builder.commandKey; + this.threadPoolKey = builder.threadPoolKey; + this.collapserKey = builder.collapserKey; + this.scope = builder.scope; + this.commandProperties = builder.commandProperties; + this.collapserProperties = builder.collapserProperties; + this.threadPoolProperties = builder.threadPoolProperties; + } + + public static Builder builder(){ + return new Builder(); + } + + + /** + * Creates instance of {@link HystrixCommand.Setter}. + * + * @return the instance of {@link HystrixCommand.Setter} + */ + public HystrixCommand.Setter build() throws HystrixPropertyException { + HystrixCommand.Setter setter = HystrixCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) + .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); + if (StringUtils.isNotBlank(threadPoolKey)) { + setter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey)); + } + try { + setter.andThreadPoolPropertiesDefaults(HystrixPropertiesManager.initializeThreadPoolProperties(threadPoolProperties)); + } catch (IllegalArgumentException e) { + throw new HystrixPropertyException("Failed to set Thread Pool properties. " + getInfo(), e); + } + try { + setter.andCommandPropertiesDefaults(HystrixPropertiesManager.initializeCommandProperties(commandProperties)); + } catch (IllegalArgumentException e) { + throw new HystrixPropertyException("Failed to set Command properties. " + getInfo(), e); + } + return setter; + } + + // todo dmgcodevil: it would be better to reuse the code from build() method + public HystrixObservableCommand.Setter buildObservableCommandSetter() { + HystrixObservableCommand.Setter setter = HystrixObservableCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) + .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); + try { + setter.andCommandPropertiesDefaults(HystrixPropertiesManager.initializeCommandProperties(commandProperties)); + } catch (IllegalArgumentException e) { + throw new HystrixPropertyException("Failed to set Command properties. " + getInfo(), e); + } + return setter; + } + + public HystrixCollapser.Setter buildCollapserCommandSetter(){ + HystrixCollapserProperties.Setter propSetter = initializeCollapserProperties(collapserProperties); + return HystrixCollapser.Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey(collapserKey)).andScope(scope) + .andCollapserPropertiesDefaults(propSetter); + } + + private String getInfo() { + return "groupKey: '" + groupKey + "', commandKey: '" + commandKey + "', threadPoolKey: '" + threadPoolKey + "'"; + } + + + public static class Builder { + private String groupKey; + private String commandKey; + private String threadPoolKey; + private String collapserKey; + private HystrixCollapser.Scope scope; + private List commandProperties = Collections.emptyList(); + private List collapserProperties = Collections.emptyList(); + private List threadPoolProperties = Collections.emptyList(); + + public Builder groupKey(String pGroupKey) { + this.groupKey = pGroupKey; + return this; + } + + public Builder groupKey(String pGroupKey, String def) { + this.groupKey = StringUtils.isNotEmpty(pGroupKey) ? pGroupKey : def; + return this; + } + + public Builder commandKey(String pCommandKey) { + this.commandKey = pCommandKey; + return this; + } + + @Deprecated + public Builder commandKey(String pCommandKey, String def) { + this.commandKey = StringUtils.isNotEmpty(pCommandKey) ? pCommandKey : def; + return this; + } + + public Builder collapserKey(String pCollapserKey) { + this.collapserKey = pCollapserKey; + return this; + } + + public Builder scope(HystrixCollapser.Scope pScope) { + this.scope = pScope; + return this; + } + + public Builder collapserProperties(List properties) { + collapserProperties = properties; + return this; + } + + public Builder commandProperties(List properties) { + commandProperties = properties; + return this; + } + + + public Builder threadPoolProperties(List properties) { + threadPoolProperties = properties; + return this; + } + + public Builder threadPoolKey(String pThreadPoolKey) { + this.threadPoolKey = pThreadPoolKey; + return this; + } + + public GenericSetterBuilder build(){ + return new GenericSetterBuilder(this); + } + + } + + + + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java new file mode 100644 index 0000000..2b91c69 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java @@ -0,0 +1,184 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import com.google.common.collect.ImmutableList; +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; + +import javax.annotation.concurrent.Immutable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Builder contains all necessary information required to create specific hystrix command. + * + * @author dmgcodevil + */ +@Immutable +public class HystrixCommandBuilder { + + private final GenericSetterBuilder setterBuilder; + private final CommandActions commandActions; + private final CacheInvocationContext cacheResultInvocationContext; + private final CacheInvocationContext cacheRemoveInvocationContext; + private final Collection> collapsedRequests; + private final List> ignoreExceptions; + private final ExecutionType executionType; + + public HystrixCommandBuilder(Builder builder) { + this.setterBuilder = builder.setterBuilder; + this.commandActions = builder.commandActions; + this.cacheResultInvocationContext = builder.cacheResultInvocationContext; + this.cacheRemoveInvocationContext = builder.cacheRemoveInvocationContext; + this.collapsedRequests = builder.collapsedRequests; + this.ignoreExceptions = builder.ignoreExceptions; + this.executionType = builder.executionType; + } + + public static Builder builder() { + return new Builder(); + } + + public GenericSetterBuilder getSetterBuilder() { + return setterBuilder; + } + + public CommandActions getCommandActions() { + return commandActions; + } + + public CacheInvocationContext getCacheResultInvocationContext() { + return cacheResultInvocationContext; + } + + public CacheInvocationContext getCacheRemoveInvocationContext() { + return cacheRemoveInvocationContext; + } + + public Collection> getCollapsedRequests() { + return collapsedRequests; + } + + public List> getIgnoreExceptions() { + return ignoreExceptions; + } + + public ExecutionType getExecutionType() { + return executionType; + } + + + public static class Builder { + private GenericSetterBuilder setterBuilder; + private CommandActions commandActions; + private CacheInvocationContext cacheResultInvocationContext; + private CacheInvocationContext cacheRemoveInvocationContext; + private Collection> collapsedRequests = Collections.emptyList(); + private List> ignoreExceptions = Collections.emptyList(); + private ExecutionType executionType = ExecutionType.SYNCHRONOUS; + + /** + * Sets the builder to create specific Hystrix setter, for instance HystrixCommand.Setter + * + * @param pSetterBuilder the builder to create specific Hystrix setter + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder setterBuilder(GenericSetterBuilder pSetterBuilder) { + this.setterBuilder = pSetterBuilder; + return this; + } + + /** + * Sets command actions {@link CommandActions}. + * + * @param pCommandActions the command actions + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder commandActions(CommandActions pCommandActions) { + this.commandActions = pCommandActions; + return this; + } + + /** + * Sets CacheResult invocation context, see {@link CacheInvocationContext} and {@link CacheResult}. + * + * @param pCacheResultInvocationContext the CacheResult invocation context + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder cacheResultInvocationContext(CacheInvocationContext pCacheResultInvocationContext) { + this.cacheResultInvocationContext = pCacheResultInvocationContext; + return this; + } + + /** + * Sets CacheRemove invocation context, see {@link CacheInvocationContext} and {@link CacheRemove}. + * + * @param pCacheRemoveInvocationContext the CacheRemove invocation context + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder cacheRemoveInvocationContext(CacheInvocationContext pCacheRemoveInvocationContext) { + this.cacheRemoveInvocationContext = pCacheRemoveInvocationContext; + return this; + } + + /** + * Sets collapsed requests. + * + * @param pCollapsedRequests the collapsed requests + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder collapsedRequests(Collection> pCollapsedRequests) { + this.collapsedRequests = pCollapsedRequests; + return this; + } + + /** + * Sets exceptions that should be ignored and wrapped to throw in {@link com.netflix.hystrix.exception.HystrixBadRequestException}. + * + * @param pIgnoreExceptions the exceptions to be ignored + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder ignoreExceptions(List> pIgnoreExceptions) { + this.ignoreExceptions = ImmutableList.copyOf(pIgnoreExceptions); + return this; + } + + /** + * Sets execution type, see {@link ExecutionType}. + * + * @param pExecutionType the execution type + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder executionType(ExecutionType pExecutionType) { + this.executionType = pExecutionType; + return this; + } + + /** + * Creates new {@link HystrixCommandBuilder} instance. + * + * @return new {@link HystrixCommandBuilder} instance + */ + public HystrixCommandBuilder build() { + return new HystrixCommandBuilder(this); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java new file mode 100644 index 0000000..23b75e8 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java @@ -0,0 +1,158 @@ +/** + * 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.contrib.javanica.command; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; +import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; +import org.apache.commons.lang3.Validate; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; + +import static com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory.createCacheRemoveInvocationContext; +import static com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory.createCacheResultInvocationContext; +import static com.netflix.hystrix.contrib.javanica.utils.EnvUtils.isCompileWeaving; +import static com.netflix.hystrix.contrib.javanica.utils.ajc.AjcUtils.getAjcMethodAroundAdvice; + +/** + * Created by dmgcodevil. + */ +public class HystrixCommandBuilderFactory { + + // todo Add Cache + + private static final HystrixCommandBuilderFactory INSTANCE = new HystrixCommandBuilderFactory(); + + public static HystrixCommandBuilderFactory getInstance() { + return INSTANCE; + } + + private HystrixCommandBuilderFactory() { + + } + + public HystrixCommandBuilder create(MetaHolder metaHolder) { + return create(metaHolder, Collections.>emptyList()); + } + + public HystrixCommandBuilder create(MetaHolder metaHolder, Collection> collapsedRequests) { + validateMetaHolder(metaHolder); + + return HystrixCommandBuilder.builder() + .setterBuilder(createGenericSetterBuilder(metaHolder)) + .commandActions(createCommandActions(metaHolder)) + .collapsedRequests(collapsedRequests) + .cacheResultInvocationContext(createCacheResultInvocationContext(metaHolder)) + .cacheRemoveInvocationContext(createCacheRemoveInvocationContext(metaHolder)) + .ignoreExceptions(metaHolder.getCommandIgnoreExceptions()) + .executionType(metaHolder.getExecutionType()) + .build(); + } + + private void validateMetaHolder(MetaHolder metaHolder) { + Validate.notNull(metaHolder, "metaHolder is required parameter and cannot be null"); + Validate.isTrue(metaHolder.isCommandAnnotationPresent(), "hystrixCommand annotation is absent"); + } + + private GenericSetterBuilder createGenericSetterBuilder(MetaHolder metaHolder) { + GenericSetterBuilder.Builder setterBuilder = GenericSetterBuilder.builder() + .groupKey(metaHolder.getCommandGroupKey()) + .threadPoolKey(metaHolder.getThreadPoolKey()) + .commandKey(metaHolder.getCommandKey()) + .collapserKey(metaHolder.getCollapserKey()) + .commandProperties(metaHolder.getCommandProperties()) + .threadPoolProperties(metaHolder.getThreadPoolProperties()) + .collapserProperties(metaHolder.getCollapserProperties()); + if (metaHolder.isCollapserAnnotationPresent()) { + setterBuilder.scope(metaHolder.getHystrixCollapser().scope()); + } + return setterBuilder.build(); + } + + private CommandActions createCommandActions(MetaHolder metaHolder) { + CommandAction commandAction = createCommandAction(metaHolder); + CommandAction fallbackAction = createFallbackAction(metaHolder); + return CommandActions.builder().commandAction(commandAction) + .fallbackAction(fallbackAction).build(); + } + + private CommandAction createCommandAction(MetaHolder metaHolder) { + return new MethodExecutionAction(metaHolder.getObj(), metaHolder.getMethod(), metaHolder.getArgs(), metaHolder); + } + + private CommandAction createFallbackAction(MetaHolder metaHolder) { + + FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(metaHolder.getObj().getClass(), + metaHolder.getMethod(), metaHolder.isExtendedFallback()); + fallbackMethod.validateReturnType(metaHolder.getMethod()); + CommandAction fallbackAction = null; + if (fallbackMethod.isPresent()) { + + Method fMethod = fallbackMethod.getMethod(); + Object[] args = fallbackMethod.isDefault() ? new Object[0] : metaHolder.getArgs(); + if (fallbackMethod.isCommand()) { + fMethod.setAccessible(true); + HystrixCommand hystrixCommand = fMethod.getAnnotation(HystrixCommand.class); + MetaHolder fmMetaHolder = MetaHolder.builder() + .obj(metaHolder.getObj()) + .method(fMethod) + .ajcMethod(getAjcMethod(metaHolder.getObj(), fMethod)) + .args(args) + .fallback(true) + .defaultFallback(fallbackMethod.isDefault()) + .defaultCollapserKey(metaHolder.getDefaultCollapserKey()) + .fallbackMethod(fMethod) + .extendedFallback(fallbackMethod.isExtended()) + .fallbackExecutionType(fallbackMethod.getExecutionType()) + .extendedParentFallback(metaHolder.isExtendedFallback()) + .observable(ExecutionType.OBSERVABLE == fallbackMethod.getExecutionType()) + .defaultCommandKey(fMethod.getName()) + .defaultGroupKey(metaHolder.getDefaultGroupKey()) + .defaultThreadPoolKey(metaHolder.getDefaultThreadPoolKey()) + .defaultProperties(metaHolder.getDefaultProperties().orNull()) + .hystrixCollapser(metaHolder.getHystrixCollapser()) + .observableExecutionMode(hystrixCommand.observableExecutionMode()) + .hystrixCommand(hystrixCommand).build(); + fallbackAction = new LazyCommandExecutionAction(fmMetaHolder); + } else { + MetaHolder fmMetaHolder = MetaHolder.builder() + .obj(metaHolder.getObj()) + .defaultFallback(fallbackMethod.isDefault()) + .method(fMethod) + .fallbackExecutionType(ExecutionType.SYNCHRONOUS) + .extendedFallback(fallbackMethod.isExtended()) + .extendedParentFallback(metaHolder.isExtendedFallback()) + .ajcMethod(null) // if fallback method isn't annotated with command annotation then we don't need to get ajc method for this + .args(args).build(); + + fallbackAction = new MethodExecutionAction(fmMetaHolder.getObj(), fMethod, fmMetaHolder.getArgs(), fmMetaHolder); + } + + } + return fallbackAction; + } + + private Method getAjcMethod(Object target, Method fallback) { + if (isCompileWeaving()) { + return getAjcMethodAroundAdvice(target.getClass(), fallback); + } + return null; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandFactory.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandFactory.java new file mode 100644 index 0000000..1dd12f3 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandFactory.java @@ -0,0 +1,57 @@ +/** + * 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.contrib.javanica.command; + +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.contrib.javanica.collapser.CommandCollapser; + +/** + * Created by dmgcodevil. + */ +public class HystrixCommandFactory { + + private static final HystrixCommandFactory INSTANCE = new HystrixCommandFactory(); + + private HystrixCommandFactory() { + + } + + public static HystrixCommandFactory getInstance() { + return INSTANCE; + } + + public HystrixInvokable create(MetaHolder metaHolder) { + HystrixInvokable executable; + if (metaHolder.isCollapserAnnotationPresent()) { + executable = new CommandCollapser(metaHolder); + } else if (metaHolder.isObservable()) { + executable = new GenericObservableCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); + } else { + executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); + } + return executable; + } + + public HystrixInvokable createDelayed(MetaHolder metaHolder) { + HystrixInvokable executable; + if (metaHolder.isObservable()) { + executable = new GenericObservableCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); + } else { + executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); + } + return executable; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/LazyCommandExecutionAction.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/LazyCommandExecutionAction.java new file mode 100644 index 0000000..20cd874 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/LazyCommandExecutionAction.java @@ -0,0 +1,111 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + + +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; +import org.apache.commons.lang3.StringUtils; + +/** + * This action creates related hystrix commands on demand when command creation can be postponed. + */ +public class LazyCommandExecutionAction implements CommandAction { + + private MetaHolder originalMetaHolder; + + + public LazyCommandExecutionAction(MetaHolder metaHolder) { + this.originalMetaHolder = metaHolder; + } + + @Override + public MetaHolder getMetaHolder() { + return originalMetaHolder; + } + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionType executionType) throws CommandActionExecutionException { + HystrixInvokable command = HystrixCommandFactory.getInstance().createDelayed(createCopy(originalMetaHolder, executionType)); + return new CommandExecutionAction(command, originalMetaHolder).execute(executionType); + } + + /** + * {@inheritDoc} + */ + @Override + public Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException { + HystrixInvokable command = HystrixCommandFactory.getInstance().createDelayed(createCopy(originalMetaHolder, executionType, args)); + return new CommandExecutionAction(command, originalMetaHolder).execute(executionType); + } + + /** + * {@inheritDoc} + */ + @Override + public String getActionName() { + return StringUtils.isNotEmpty(originalMetaHolder.getHystrixCommand().commandKey()) ? + originalMetaHolder.getHystrixCommand().commandKey() + : originalMetaHolder.getDefaultCommandKey(); + } + + // todo dmgcodevil: move it to MetaHolder class ? + private MetaHolder createCopy(MetaHolder source, ExecutionType executionType) { + return MetaHolder.builder() + .obj(source.getObj()) + .method(source.getMethod()) + .ajcMethod(source.getAjcMethod()) + .fallbackExecutionType(source.getFallbackExecutionType()) + .extendedFallback(source.isExtendedFallback()) + .extendedParentFallback(source.isExtendedParentFallback()) + .executionType(executionType) + .args(source.getArgs()) + .observable(source.isObservable()) + .observableExecutionMode(source.getObservableExecutionMode()) + .defaultCollapserKey(source.getDefaultCollapserKey()) + .defaultCommandKey(source.getDefaultCommandKey()) + .defaultGroupKey(source.getDefaultGroupKey()) + .defaultThreadPoolKey(source.getDefaultThreadPoolKey()) + .defaultProperties(source.getDefaultProperties().orNull()) + .hystrixCollapser(source.getHystrixCollapser()) + .hystrixCommand(source.getHystrixCommand()).build(); + } + + private MetaHolder createCopy(MetaHolder source, ExecutionType executionType, Object[] args) { + return MetaHolder.builder() + .obj(source.getObj()) + .method(source.getMethod()) + .executionType(executionType) + .ajcMethod(source.getAjcMethod()) + .fallbackExecutionType(source.getFallbackExecutionType()) + .extendedParentFallback(source.isExtendedParentFallback()) + .extendedFallback(source.isExtendedFallback()) + .args(args) + .observable(source.isObservable()) + .observableExecutionMode(source.getObservableExecutionMode()) + .defaultCollapserKey(source.getDefaultCollapserKey()) + .defaultCommandKey(source.getDefaultCommandKey()) + .defaultGroupKey(source.getDefaultGroupKey()) + .defaultThreadPoolKey(source.getDefaultThreadPoolKey()) + .defaultProperties(source.getDefaultProperties().orNull()) + .hystrixCollapser(source.getHystrixCollapser()) + .hystrixCommand(source.getHystrixCommand()).build(); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java new file mode 100644 index 0000000..139f5dc --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java @@ -0,0 +1,514 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.netflix.hystrix.contrib.javanica.annotation.*; +import com.netflix.hystrix.contrib.javanica.command.closure.Closure; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Simple immutable holder to keep all necessary information about current method to build Hystrix command. + */ +// todo: replace fallback related flags with FallbackMethod class +@Immutable +public final class MetaHolder { + + private final HystrixCollapser hystrixCollapser; + private final HystrixCommand hystrixCommand; + private final DefaultProperties defaultProperties; + + private final Method method; + private final Method cacheKeyMethod; + private final Method ajcMethod; + private final Method fallbackMethod; + private final Object obj; + private final Object proxyObj; + private final Object[] args; + private final Closure closure; + private final String defaultGroupKey; + private final String defaultCommandKey; + private final String defaultCollapserKey; + private final String defaultThreadPoolKey; + private final ExecutionType executionType; + private final boolean extendedFallback; + private final ExecutionType collapserExecutionType; + private final ExecutionType fallbackExecutionType; + private final boolean fallback; + private boolean extendedParentFallback; + private final boolean defaultFallback; + private final JoinPoint joinPoint; + private final boolean observable; + private final ObservableExecutionMode observableExecutionMode; + + private static final Function identityFun = new Function() { + @Nullable + @Override + public Object apply(@Nullable Object input) { + return input; + } + }; + + private MetaHolder(Builder builder) { + this.hystrixCommand = builder.hystrixCommand; + this.method = builder.method; + this.cacheKeyMethod = builder.cacheKeyMethod; + this.fallbackMethod = builder.fallbackMethod; + this.ajcMethod = builder.ajcMethod; + this.obj = builder.obj; + this.proxyObj = builder.proxyObj; + this.args = builder.args; + this.closure = builder.closure; + this.defaultGroupKey = builder.defaultGroupKey; + this.defaultCommandKey = builder.defaultCommandKey; + this.defaultThreadPoolKey = builder.defaultThreadPoolKey; + this.defaultCollapserKey = builder.defaultCollapserKey; + this.defaultProperties = builder.defaultProperties; + this.hystrixCollapser = builder.hystrixCollapser; + this.executionType = builder.executionType; + this.collapserExecutionType = builder.collapserExecutionType; + this.fallbackExecutionType = builder.fallbackExecutionType; + this.joinPoint = builder.joinPoint; + this.extendedFallback = builder.extendedFallback; + this.defaultFallback = builder.defaultFallback; + this.fallback = builder.fallback; + this.extendedParentFallback = builder.extendedParentFallback; + this.observable = builder.observable; + this.observableExecutionMode = builder.observableExecutionMode; + } + + public static Builder builder() { + return new Builder(); + } + + public HystrixCollapser getHystrixCollapser() { + return hystrixCollapser; + } + + public HystrixCommand getHystrixCommand() { + return hystrixCommand; + } + + public Method getMethod() { + return method; + } + + public Method getCacheKeyMethod() { + return cacheKeyMethod; + } + + public Method getAjcMethod() { + return ajcMethod; + } + + public Object getObj() { + return obj; + } + + public Object getProxyObj() { + return proxyObj; + } + + public Closure getClosure() { + return closure; + } + + public ExecutionType getExecutionType() { + return executionType; + } + + public ExecutionType getCollapserExecutionType() { + return collapserExecutionType; + } + + public Object[] getArgs() { + return args != null ? Arrays.copyOf(args, args.length) : new Object[]{}; + } + + public String getCommandGroupKey() { + return isCommandAnnotationPresent() ? get(hystrixCommand.groupKey(), defaultGroupKey) : ""; + } + + public String getDefaultGroupKey() { + return defaultGroupKey; + } + + public String getDefaultThreadPoolKey() { + return defaultThreadPoolKey; + } + + public String getCollapserKey() { + return isCollapserAnnotationPresent() ? get(hystrixCollapser.collapserKey(), defaultCollapserKey) : ""; + } + + public String getCommandKey() { + return isCommandAnnotationPresent() ? get(hystrixCommand.commandKey(), defaultCommandKey) : ""; + } + + public String getThreadPoolKey() { + return isCommandAnnotationPresent() ? get(hystrixCommand.threadPoolKey(), defaultThreadPoolKey) : ""; + } + + public String getDefaultCommandKey() { + return defaultCommandKey; + } + + public String getDefaultCollapserKey() { + return defaultCollapserKey; + } + + public boolean hasDefaultProperties() { + return defaultProperties != null; + } + + public Optional getDefaultProperties() { + return Optional.fromNullable(defaultProperties); + } + + public Class[] getParameterTypes() { + return method.getParameterTypes(); + } + + public boolean isCollapserAnnotationPresent() { + return hystrixCollapser != null; + } + + public boolean isCommandAnnotationPresent() { + return hystrixCommand != null; + } + + public JoinPoint getJoinPoint() { + return joinPoint; + } + + public Method getFallbackMethod() { + return fallbackMethod; + } + + public boolean hasFallbackMethod() { + return fallbackMethod != null; + } + + public boolean isExtendedParentFallback() { + return extendedParentFallback; + } + + public boolean hasFallbackMethodCommand() { + return fallbackMethod != null && fallbackMethod.isAnnotationPresent(HystrixCommand.class); + } + + public boolean isFallback() { + return fallback; + } + + public boolean isExtendedFallback() { + return extendedFallback; + } + + public boolean isDefaultFallback() { + return defaultFallback; + } + + @SuppressWarnings("unchecked") + public List> getCommandIgnoreExceptions() { + if (!isCommandAnnotationPresent()) return Collections.emptyList(); + return getOrDefault(new Supplier>>() { + @Override + public List> get() { + return ImmutableList.>copyOf(hystrixCommand.ignoreExceptions()); + } + }, new Supplier>>() { + @Override + public List> get() { + return hasDefaultProperties() + ? ImmutableList.>copyOf(defaultProperties.ignoreExceptions()) + : Collections.>emptyList(); + } + }, this.>nonEmptyList()); + } + + public ExecutionType getFallbackExecutionType() { + return fallbackExecutionType; + } + + public List getCommandProperties() { + if (!isCommandAnnotationPresent()) return Collections.emptyList(); + return getOrDefault(new Supplier>() { + @Override + public List get() { + return ImmutableList.copyOf(hystrixCommand.commandProperties()); + } + }, new Supplier>() { + @Override + public List get() { + return hasDefaultProperties() + ? ImmutableList.copyOf(defaultProperties.commandProperties()) + : Collections.emptyList(); + } + }, this.nonEmptyList()); + } + + public List getCollapserProperties() { + return isCollapserAnnotationPresent() ? ImmutableList.copyOf(hystrixCollapser.collapserProperties()) : Collections.emptyList(); + } + + public List getThreadPoolProperties() { + if (!isCommandAnnotationPresent()) return Collections.emptyList(); + return getOrDefault(new Supplier>() { + @Override + public List get() { + return ImmutableList.copyOf(hystrixCommand.threadPoolProperties()); + } + }, new Supplier>() { + @Override + public List get() { + return hasDefaultProperties() + ? ImmutableList.copyOf(defaultProperties.threadPoolProperties()) + : Collections.emptyList(); + } + }, this.nonEmptyList()); + } + + public boolean isObservable() { + return observable; + } + + public ObservableExecutionMode getObservableExecutionMode() { + return observableExecutionMode; + } + + public boolean raiseHystrixExceptionsContains(HystrixException hystrixException) { + return getRaiseHystrixExceptions().contains(hystrixException); + } + + public List getRaiseHystrixExceptions() { + return getOrDefault(new Supplier>() { + @Override + public List get() { + return ImmutableList.copyOf(hystrixCommand.raiseHystrixExceptions()); + } + }, new Supplier>() { + @Override + public List get() { + return hasDefaultProperties() + ? ImmutableList.copyOf(defaultProperties.raiseHystrixExceptions()) + : Collections.emptyList(); + + } + }, this.nonEmptyList()); + } + + private String get(String key, String defaultKey) { + return StringUtils.isNotBlank(key) ? key : defaultKey; + } + + private Predicate> nonEmptyList() { + return new Predicate>() { + @Override + public boolean apply(@Nullable List input) { + return input != null && !input.isEmpty(); + } + }; + } + + @SuppressWarnings("unchecked") + private T getOrDefault(Supplier source, Supplier defaultChoice, Predicate isDefined) { + return getOrDefault(source, defaultChoice, isDefined, (Function) identityFun); + } + + private T getOrDefault(Supplier source, Supplier defaultChoice, Predicate isDefined, Function map) { + T res = source.get(); + if (!isDefined.apply(res)) { + res = defaultChoice.get(); + } + return map.apply(res); + } + + public static final class Builder { + + private static final Class[] EMPTY_ARRAY_OF_TYPES= new Class[0]; + + private HystrixCollapser hystrixCollapser; + private HystrixCommand hystrixCommand; + private DefaultProperties defaultProperties; + private Method method; + private Method cacheKeyMethod; + private Method fallbackMethod; + private Method ajcMethod; + private Object obj; + private Object proxyObj; + private Closure closure; + private Object[] args; + private String defaultGroupKey; + private String defaultCommandKey; + private String defaultCollapserKey; + private String defaultThreadPoolKey; + private ExecutionType executionType; + private ExecutionType collapserExecutionType; + private ExecutionType fallbackExecutionType; + private boolean extendedFallback; + private boolean fallback; + private boolean extendedParentFallback; + private boolean defaultFallback; + private boolean observable; + private JoinPoint joinPoint; + private ObservableExecutionMode observableExecutionMode; + + public Builder hystrixCollapser(HystrixCollapser hystrixCollapser) { + this.hystrixCollapser = hystrixCollapser; + return this; + } + + public Builder hystrixCommand(HystrixCommand hystrixCommand) { + this.hystrixCommand = hystrixCommand; + return this; + } + + public Builder method(Method method) { + this.method = method; + return this; + } + + public Builder cacheKeyMethod(Method cacheKeyMethod) { + this.cacheKeyMethod = cacheKeyMethod; + return this; + } + + public Builder fallbackMethod(Method fallbackMethod) { + this.fallbackMethod = fallbackMethod; + return this; + } + + public Builder fallbackExecutionType(ExecutionType fallbackExecutionType) { + this.fallbackExecutionType = fallbackExecutionType; + return this; + } + + public Builder fallback(boolean fallback) { + this.fallback = fallback; + return this; + } + + public Builder extendedParentFallback(boolean extendedParentFallback) { + this.extendedParentFallback = extendedParentFallback; + return this; + } + + public Builder defaultFallback(boolean defaultFallback) { + this.defaultFallback = defaultFallback; + return this; + } + + public Builder ajcMethod(Method ajcMethod) { + this.ajcMethod = ajcMethod; + return this; + } + + public Builder obj(Object obj) { + this.obj = obj; + return this; + } + + public Builder proxyObj(Object proxy) { + this.proxyObj = proxy; + return this; + } + + public Builder args(Object[] args) { + this.args = args; + return this; + } + + public Builder closure(Closure closure) { + this.closure = closure; + return this; + } + + public Builder executionType(ExecutionType executionType) { + this.executionType = executionType; + return this; + } + + public Builder collapserExecutionType(ExecutionType collapserExecutionType) { + this.collapserExecutionType = collapserExecutionType; + return this; + } + + public Builder defaultGroupKey(String defGroupKey) { + this.defaultGroupKey = defGroupKey; + return this; + } + + public Builder defaultCommandKey(String defCommandKey) { + this.defaultCommandKey = defCommandKey; + return this; + } + + public Builder defaultThreadPoolKey(String defaultThreadPoolKey) { + this.defaultThreadPoolKey = defaultThreadPoolKey; + return this; + } + + public Builder defaultCollapserKey(String defCollapserKey) { + this.defaultCollapserKey = defCollapserKey; + return this; + } + + public Builder defaultProperties(@Nullable DefaultProperties defaultProperties) { + this.defaultProperties = defaultProperties; + return this; + } + + public Builder joinPoint(JoinPoint joinPoint) { + this.joinPoint = joinPoint; + return this; + } + + public Builder extendedFallback(boolean extendedFallback) { + this.extendedFallback = extendedFallback; + return this; + } + + public Builder observable(boolean observable) { + this.observable = observable; + return this; + } + + public Builder observableExecutionMode(ObservableExecutionMode observableExecutionMode) { + this.observableExecutionMode = observableExecutionMode; + return this; + } + + public MetaHolder build() { + return new MetaHolder(this); + } + + + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MethodExecutionAction.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MethodExecutionAction.java new file mode 100644 index 0000000..2adaae9 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MethodExecutionAction.java @@ -0,0 +1,148 @@ +/** + * Copyright 2012 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.contrib.javanica.command; + + +import com.netflix.hystrix.contrib.javanica.command.closure.AsyncClosureFactory; +import com.netflix.hystrix.contrib.javanica.command.closure.Closure; +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; +import com.netflix.hystrix.contrib.javanica.exception.ExceptionUtils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static com.netflix.hystrix.contrib.javanica.utils.EnvUtils.isCompileWeaving; +import static com.netflix.hystrix.contrib.javanica.utils.ajc.AjcUtils.invokeAjcMethod; + +/** + * This implementation invokes methods using java reflection. + * If {@link Method#invoke(Object, Object...)} throws exception then this exception is wrapped to {@link CommandActionExecutionException} + * for further unwrapping and processing. + */ +public class MethodExecutionAction implements CommandAction { + + private static final Object[] EMPTY_ARGS = new Object[]{}; + + private final Object object; + private final Method method; + private final Object[] _args; + private final MetaHolder metaHolder; + + + public MethodExecutionAction(Object object, Method method, MetaHolder metaHolder) { + this.object = object; + this.method = method; + this._args = EMPTY_ARGS; + this.metaHolder = metaHolder; + } + + public MethodExecutionAction(Object object, Method method, Object[] args, MetaHolder metaHolder){ + this.object = object; + this.method = method; + this._args = args; + this.metaHolder = metaHolder; + } + + public Object getObject() { + return object; + } + + public Method getMethod() { + return method; + } + + public Object[] getArgs() { + return _args; + } + + @Override + public MetaHolder getMetaHolder() { + return metaHolder; + } + + @Override + public Object execute(ExecutionType executionType) throws CommandActionExecutionException { + return executeWithArgs(executionType, _args); + } + + /** + * Invokes the method. Also private method also can be invoked. + * + * @return result of execution + */ + @Override + public Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException { + if(ExecutionType.ASYNCHRONOUS == executionType){ + Closure closure = AsyncClosureFactory.getInstance().createClosure(metaHolder, method, object, args); + return executeClj(closure.getClosureObj(), closure.getClosureMethod()); + } + + return execute(object, method, args); + } + + /** + * {@inheritDoc} + */ + @Override + public String getActionName() { + return method.getName(); + } + + /** + * Invokes the method. + * + * @return result of execution + */ + private Object execute(Object o, Method m, Object... args) throws CommandActionExecutionException { + Object result = null; + try { + m.setAccessible(true); // suppress Java language access + if (isCompileWeaving() && metaHolder.getAjcMethod() != null) { + result = invokeAjcMethod(metaHolder.getAjcMethod(), o, metaHolder, args); + } else { + result = m.invoke(o, args); + } + } catch (IllegalAccessException e) { + propagateCause(e); + } catch (InvocationTargetException e) { + propagateCause(e); + } + return result; + } + + private Object executeClj(Object o, Method m, Object... args){ + Object result = null; + try { + m.setAccessible(true); // suppress Java language access + result = m.invoke(o, args); + } catch (IllegalAccessException e) { + propagateCause(e); + } catch (InvocationTargetException e) { + propagateCause(e); + } + return result; + } + + /** + * Retrieves cause exception and wraps to {@link CommandActionExecutionException}. + * + * @param throwable the throwable + */ + private void propagateCause(Throwable throwable) throws CommandActionExecutionException { + ExceptionUtils.propagateCause(throwable); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AbstractClosureFactory.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AbstractClosureFactory.java new file mode 100644 index 0000000..23735a1 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AbstractClosureFactory.java @@ -0,0 +1,86 @@ +/** + * Copyright 2012 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.contrib.javanica.command.closure; + +import com.google.common.base.Throwables; +import com.netflix.hystrix.contrib.javanica.command.ClosureCommand; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static com.netflix.hystrix.contrib.javanica.utils.EnvUtils.isCompileWeaving; +import static com.netflix.hystrix.contrib.javanica.utils.ajc.AjcUtils.invokeAjcMethod; +import static org.slf4j.helpers.MessageFormatter.format; + +/** + * Abstract implementation of {@link ClosureFactory}. + */ +public abstract class AbstractClosureFactory implements ClosureFactory { + + static final String ERROR_TYPE_MESSAGE = "return type of '{}' method should be {}."; + static final String INVOKE_METHOD = "invoke"; + + @Override + public Closure createClosure(MetaHolder metaHolder, Method method, Object o, Object... args) { + try { + Object closureObj; + method.setAccessible(true); + if (isCompileWeaving()) { + closureObj = invokeAjcMethod(metaHolder.getAjcMethod(), o, metaHolder, args); + } else { + closureObj = method.invoke(o, args); // creates instance of an anonymous class + } + return createClosure(method.getName(), closureObj); + } catch (InvocationTargetException e) { + throw Throwables.propagate(e.getCause()); + } catch (Exception e) { + throw Throwables.propagate(e); + } + } + + /** + * Creates closure. + * + * @param rootMethodName the name of external method within which closure is created. + * @param closureObj the instance of specific anonymous class + * @return new {@link Closure} instance + * @throws Exception + */ + Closure createClosure(String rootMethodName, final Object closureObj) throws Exception { + if (!isClosureCommand(closureObj)) { + throw new RuntimeException(format(ERROR_TYPE_MESSAGE, rootMethodName, + getClosureCommandType().getName()).getMessage()); + } + Method closureMethod = closureObj.getClass().getMethod(INVOKE_METHOD); + return new Closure(closureMethod, closureObj); + } + + /** + * Checks that closureObj is instance of necessary class. + * + * @param closureObj the instance of an anonymous class + * @return true of closureObj has expected type, otherwise - false + */ + abstract boolean isClosureCommand(final Object closureObj); + + /** + * Gets type of expected closure type. + * + * @return closure (anonymous class) type + */ + abstract Class getClosureCommandType(); +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AsyncClosureFactory.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AsyncClosureFactory.java new file mode 100644 index 0000000..c950254 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AsyncClosureFactory.java @@ -0,0 +1,51 @@ +/** + * Copyright 2012 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.contrib.javanica.command.closure; + +import com.netflix.hystrix.contrib.javanica.command.AsyncResult; +import com.netflix.hystrix.contrib.javanica.command.ClosureCommand; + +/** + * Specific implementation of {@link ClosureFactory}. + */ +public class AsyncClosureFactory extends AbstractClosureFactory { + + private static final AsyncClosureFactory INSTANCE = new AsyncClosureFactory(); + + private AsyncClosureFactory() { + } + + public static AsyncClosureFactory getInstance() { + return INSTANCE; + } + + /** + * {@inheritDoc}. + */ + @Override + boolean isClosureCommand(Object closureObj) { + return closureObj instanceof AsyncResult; + } + + /** + * {@inheritDoc}. + */ + @Override + Class getClosureCommandType() { + return AsyncResult.class; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/Closure.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/Closure.java new file mode 100644 index 0000000..e3b1fe7 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/Closure.java @@ -0,0 +1,47 @@ +/** + * Copyright 2012 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.contrib.javanica.command.closure; + +import java.lang.reflect.Method; + +/** + * Contains method and instance of anonymous class which implements + * {@link com.netflix.hystrix.contrib.javanica.command.ClosureCommand} interface. + */ +public class Closure { + + private final Method closureMethod; + private final Object closureObj; + + public Closure() { + closureMethod = null; + closureObj = null; + } + + public Closure(Method closureMethod, Object closureObj) { + this.closureMethod = closureMethod; + this.closureObj = closureObj; + } + + public Method getClosureMethod() { + return closureMethod; + } + + public Object getClosureObj() { + return closureObj; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactory.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactory.java new file mode 100644 index 0000000..bae427f --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactory.java @@ -0,0 +1,37 @@ +/** + * Copyright 2012 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.contrib.javanica.command.closure; + +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; + +import java.lang.reflect.Method; + +/** + * Factory to create instances of {@link Closure} class. + */ +public interface ClosureFactory { + + /** + * Creates closure in accordance with method return type. + * + * @param metaHolder the meta holder + * @param method the external method within which closure is created + * @param o the object the underlying method is invoked from + * @param args the arguments used for the method call + * @return new {@link Closure} instance + */ + Closure createClosure(final MetaHolder metaHolder, final Method method, final Object o, final Object... args); +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/conf/HystrixPropertiesManager.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/conf/HystrixPropertiesManager.java new file mode 100644 index 0000000..eb9296a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/conf/HystrixPropertiesManager.java @@ -0,0 +1,427 @@ +/** + * 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.contrib.javanica.conf; + +import com.google.common.collect.ImmutableMap; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import org.apache.commons.lang3.Validate; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * This class provides methods to set hystrix properties. + */ +public final class HystrixPropertiesManager { + + private HystrixPropertiesManager() { + } + + /** + * Command execution properties. + */ + public static final String EXECUTION_ISOLATION_STRATEGY = "execution.isolation.strategy"; + public static final String EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS = "execution.isolation.thread.timeoutInMilliseconds"; + public static final String EXECUTION_TIMEOUT_ENABLED = "execution.timeout.enabled"; + public static final String EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT = "execution.isolation.thread.interruptOnTimeout"; + public static final String EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS = "execution.isolation.semaphore.maxConcurrentRequests"; + + /** + * Command fallback properties. + */ + public static final String FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS = "fallback.isolation.semaphore.maxConcurrentRequests"; + public static final String FALLBACK_ENABLED = "fallback.enabled"; + + /** + * Command circuit breaker properties. + */ + public static final String CIRCUIT_BREAKER_ENABLED = "circuitBreaker.enabled"; + public static final String CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD = "circuitBreaker.requestVolumeThreshold"; + public static final String CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS = "circuitBreaker.sleepWindowInMilliseconds"; + public static final String CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE = "circuitBreaker.errorThresholdPercentage"; + public static final String CIRCUIT_BREAKER_FORCE_OPEN = "circuitBreaker.forceOpen"; + public static final String CIRCUIT_BREAKER_FORCE_CLOSED = "circuitBreaker.forceClosed"; + + /** + * Command metrics properties. + */ + public static final String METRICS_ROLLING_PERCENTILE_ENABLED = "metrics.rollingPercentile.enabled"; + public static final String METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS = "metrics.rollingPercentile.timeInMilliseconds"; + public static final String METRICS_ROLLING_PERCENTILE_NUM_BUCKETS = "metrics.rollingPercentile.numBuckets"; + public static final String METRICS_ROLLING_PERCENTILE_BUCKET_SIZE = "metrics.rollingPercentile.bucketSize"; + public static final String METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS = "metrics.healthSnapshot.intervalInMilliseconds"; + + /** + * Command CommandRequest Context properties. + */ + public static final String REQUEST_CACHE_ENABLED = "requestCache.enabled"; + public static final String REQUEST_LOG_ENABLED = "requestLog.enabled"; + + /** + * Thread pool properties. + */ + public static final String MAX_QUEUE_SIZE = "maxQueueSize"; + public static final String CORE_SIZE = "coreSize"; + public static final String MAXIMUM_SIZE = "maximumSize"; + public static final String ALLOW_MAXIMUM_SIZE_TO_DIVERGE_FROM_CORE_SIZE = "allowMaximumSizeToDivergeFromCoreSize"; + public static final String KEEP_ALIVE_TIME_MINUTES = "keepAliveTimeMinutes"; + public static final String QUEUE_SIZE_REJECTION_THRESHOLD = "queueSizeRejectionThreshold"; + public static final String METRICS_ROLLING_STATS_NUM_BUCKETS = "metrics.rollingStats.numBuckets"; + public static final String METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS = "metrics.rollingStats.timeInMilliseconds"; + + /** + * Collapser properties. + */ + public static final String MAX_REQUESTS_IN_BATCH = "maxRequestsInBatch"; + public static final String TIMER_DELAY_IN_MILLISECONDS = "timerDelayInMilliseconds"; + + /** + * Creates and sets Hystrix command properties. + * + * @param properties the collapser properties + */ + public static HystrixCommandProperties.Setter initializeCommandProperties(List properties) throws IllegalArgumentException { + return initializeProperties(HystrixCommandProperties.Setter(), properties, CMD_PROP_MAP, "command"); + } + + /** + * Creates and sets Hystrix thread pool properties. + * + * @param properties the collapser properties + */ + public static HystrixThreadPoolProperties.Setter initializeThreadPoolProperties(List properties) throws IllegalArgumentException { + return initializeProperties(HystrixThreadPoolProperties.Setter(), properties, TP_PROP_MAP, "thread pool"); + } + + /** + * Creates and sets Hystrix collapser properties. + * + * @param properties the collapser properties + */ + public static HystrixCollapserProperties.Setter initializeCollapserProperties(List properties) { + return initializeProperties(HystrixCollapserProperties.Setter(), properties, COLLAPSER_PROP_MAP, "collapser"); + } + + private static S initializeProperties(S setter, List properties, Map> propMap, String type) { + if (properties != null && properties.size() > 0) { + for (HystrixProperty property : properties) { + validate(property); + if (!propMap.containsKey(property.name())) { + throw new IllegalArgumentException("unknown " + type + " property: " + property.name()); + } + + propMap.get(property.name()).set(setter, property.value()); + } + } + return setter; + } + + private static void validate(HystrixProperty hystrixProperty) throws IllegalArgumentException { + Validate.notBlank(hystrixProperty.name(), "hystrix property name cannot be null or blank"); + } + + private static final Map> CMD_PROP_MAP = + ImmutableMap.>builder() + .put(EXECUTION_ISOLATION_STRATEGY, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withExecutionIsolationStrategy(toEnum(EXECUTION_ISOLATION_STRATEGY, value, HystrixCommandProperties.ExecutionIsolationStrategy.class, + HystrixCommandProperties.ExecutionIsolationStrategy.values())); + } + }) + .put(EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withExecutionTimeoutInMilliseconds(toInt(EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value)); + } + }) + .put(EXECUTION_TIMEOUT_ENABLED, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withExecutionTimeoutEnabled(toBoolean(value)); + } + }) + .put(EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withExecutionIsolationThreadInterruptOnTimeout(toBoolean(value)); + } + }) + .put(EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withExecutionIsolationSemaphoreMaxConcurrentRequests(toInt(EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value)); + } + }) + .put(FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withFallbackIsolationSemaphoreMaxConcurrentRequests(toInt(FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value)); + } + }) + .put(FALLBACK_ENABLED, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withFallbackEnabled(toBoolean(value)); + } + }) + .put(CIRCUIT_BREAKER_ENABLED, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withCircuitBreakerEnabled(toBoolean(value)); + } + }) + .put(CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withCircuitBreakerRequestVolumeThreshold(toInt(CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value)); + } + }) + .put(CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withCircuitBreakerSleepWindowInMilliseconds(toInt(CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value)); + } + }) + .put(CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withCircuitBreakerErrorThresholdPercentage(toInt(CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value)); + } + }) + .put(CIRCUIT_BREAKER_FORCE_OPEN, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withCircuitBreakerForceOpen(toBoolean(value)); + } + }) + .put(CIRCUIT_BREAKER_FORCE_CLOSED, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withCircuitBreakerForceClosed(toBoolean(value)); + } + }) + .put(METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingStatisticalWindowInMilliseconds(toInt(METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, value)); + } + }) + .put(METRICS_ROLLING_STATS_NUM_BUCKETS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingStatisticalWindowBuckets(toInt(METRICS_ROLLING_STATS_NUM_BUCKETS, value)); + } + }) + .put(METRICS_ROLLING_PERCENTILE_ENABLED, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingPercentileEnabled(toBoolean(value)); + } + }) + .put(METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingPercentileWindowInMilliseconds(toInt(METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS, value)); + } + }) + .put(METRICS_ROLLING_PERCENTILE_NUM_BUCKETS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingPercentileWindowBuckets(toInt(METRICS_ROLLING_PERCENTILE_NUM_BUCKETS, value)); + } + }) + .put(METRICS_ROLLING_PERCENTILE_BUCKET_SIZE, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingPercentileBucketSize(toInt(METRICS_ROLLING_PERCENTILE_BUCKET_SIZE, value)); + } + }) + .put(METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsHealthSnapshotIntervalInMilliseconds(toInt(METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS, value)); + } + }) + .put(REQUEST_CACHE_ENABLED, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withRequestCacheEnabled(toBoolean(value)); + } + }) + .put(REQUEST_LOG_ENABLED, new PropSetter() { + @Override + public void set(HystrixCommandProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withRequestLogEnabled(toBoolean(value)); + } + }) + .build(); + + + private static final Map> TP_PROP_MAP = + ImmutableMap.>builder() + .put(MAX_QUEUE_SIZE, new PropSetter() { + @Override + public void set(HystrixThreadPoolProperties.Setter setter, String value) { + setter.withMaxQueueSize(toInt(MAX_QUEUE_SIZE, value)); + } + }) + .put(CORE_SIZE, new PropSetter() { + @Override + public void set(HystrixThreadPoolProperties.Setter setter, String value) { + setter.withCoreSize(toInt(CORE_SIZE, value)); + } + } + ) + .put(MAXIMUM_SIZE, new PropSetter() { + @Override + public void set(HystrixThreadPoolProperties.Setter setter, String value) { + setter.withMaximumSize(toInt(MAXIMUM_SIZE, value)); + } + } + ) + .put(ALLOW_MAXIMUM_SIZE_TO_DIVERGE_FROM_CORE_SIZE, new PropSetter() { + @Override + public void set(HystrixThreadPoolProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withAllowMaximumSizeToDivergeFromCoreSize(toBoolean(value)); + } + }) + .put(KEEP_ALIVE_TIME_MINUTES, new PropSetter() { + @Override + public void set(HystrixThreadPoolProperties.Setter setter, String value) { + setter.withKeepAliveTimeMinutes(toInt(KEEP_ALIVE_TIME_MINUTES, value)); + } + } + ) + .put(QUEUE_SIZE_REJECTION_THRESHOLD, new PropSetter() { + @Override + public void set(HystrixThreadPoolProperties.Setter setter, String value) { + setter.withQueueSizeRejectionThreshold(toInt(QUEUE_SIZE_REJECTION_THRESHOLD, value)); + } + } + ) + .put(METRICS_ROLLING_STATS_NUM_BUCKETS, new PropSetter() { + @Override + public void set(HystrixThreadPoolProperties.Setter setter, String value) { + setter.withMetricsRollingStatisticalWindowBuckets(toInt(METRICS_ROLLING_STATS_NUM_BUCKETS, value)); + } + } + ) + .put(METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixThreadPoolProperties.Setter setter, String value) { + setter.withMetricsRollingStatisticalWindowInMilliseconds(toInt(METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, value)); + } + } + ) + .build(); + + + private static final Map> COLLAPSER_PROP_MAP = + ImmutableMap.>builder() + .put(TIMER_DELAY_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) { + setter.withTimerDelayInMilliseconds(toInt(TIMER_DELAY_IN_MILLISECONDS, value)); + } + }) + .put(MAX_REQUESTS_IN_BATCH, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) { + setter.withMaxRequestsInBatch(toInt(MAX_REQUESTS_IN_BATCH, value)); + } + } + ) + .put(METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingStatisticalWindowInMilliseconds(toInt(METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, value)); + } + }) + .put(METRICS_ROLLING_STATS_NUM_BUCKETS, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingStatisticalWindowBuckets(toInt(METRICS_ROLLING_STATS_NUM_BUCKETS, value)); + } + }) + .put(METRICS_ROLLING_PERCENTILE_ENABLED, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingPercentileEnabled(toBoolean(value)); + } + }) + .put(METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingPercentileWindowInMilliseconds(toInt(METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS, value)); + } + }) + .put(METRICS_ROLLING_PERCENTILE_NUM_BUCKETS, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingPercentileWindowBuckets(toInt(METRICS_ROLLING_PERCENTILE_NUM_BUCKETS, value)); + } + }) + .put(METRICS_ROLLING_PERCENTILE_BUCKET_SIZE, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withMetricsRollingPercentileBucketSize(toInt(METRICS_ROLLING_PERCENTILE_BUCKET_SIZE, value)); + } + }) + .put(REQUEST_CACHE_ENABLED, new PropSetter() { + @Override + public void set(HystrixCollapserProperties.Setter setter, String value) throws IllegalArgumentException { + setter.withRequestCacheEnabled(toBoolean(value)); + } + }) + .build(); + + + private interface PropSetter { + void set(S setter, V value) throws IllegalArgumentException; + } + + private static > E toEnum(String propName, String propValue, Class enumType, E... values) throws IllegalArgumentException { + try { + return Enum.valueOf(enumType, propValue); + } catch (NullPointerException npe) { + throw createBadEnumError(propName, propValue, values); + } catch (IllegalArgumentException e) { + throw createBadEnumError(propName, propValue, values); + } + } + + private static int toInt(String propName, String propValue) throws IllegalArgumentException { + try { + return Integer.parseInt(propValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("bad property value. property name '" + propName + "'. Expected int value, actual = " + propValue); + } + } + + private static boolean toBoolean(String propValue) { + return Boolean.valueOf(propValue); + } + + private static IllegalArgumentException createBadEnumError(String propName, String propValue, Enum... values) { + throw new IllegalArgumentException("bad property value. property name '" + propName + "'. Expected correct enum value, one of the [" + Arrays.toString(values) + "] , actual = " + propValue); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/CommandActionExecutionException.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/CommandActionExecutionException.java new file mode 100644 index 0000000..11d60f3 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/CommandActionExecutionException.java @@ -0,0 +1,37 @@ +/** + * Copyright 2012 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.contrib.javanica.exception; + +/** + * This exception is used to wrap original exceptions to push through javanica infrastructure. + */ +public class CommandActionExecutionException extends RuntimeException { + + /** + * Creates exceptions instance with cause is original exception. + * + * @param cause the original exception + */ + public CommandActionExecutionException(Throwable cause) { + super(cause); + } + + /** + * Default constructor. + */ + public CommandActionExecutionException() { + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/ExceptionUtils.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/ExceptionUtils.java new file mode 100644 index 0000000..0dab38d --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/ExceptionUtils.java @@ -0,0 +1,59 @@ +/** + * Copyright 2012 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.contrib.javanica.exception; + +import com.netflix.hystrix.exception.HystrixBadRequestException; + +/** + * Util class to work with exceptions. + */ +public class ExceptionUtils { + + /** + * Retrieves cause exception and wraps to {@link CommandActionExecutionException}. + * + * @param throwable the throwable + */ + public static void propagateCause(Throwable throwable) throws CommandActionExecutionException { + throw new CommandActionExecutionException(throwable.getCause()); + } + + /** + * Wraps cause exception to {@link CommandActionExecutionException}. + * + * @param throwable the throwable + */ + public static CommandActionExecutionException wrapCause(Throwable throwable) { + return new CommandActionExecutionException(throwable.getCause()); + } + + /** + * Gets actual exception if it's wrapped in {@link CommandActionExecutionException} or {@link HystrixBadRequestException}. + * + * @param e the exception + * @return unwrapped + */ + public static Throwable unwrapCause(Throwable e) { + if (e instanceof CommandActionExecutionException) { + return e.getCause(); + } + if (e instanceof HystrixBadRequestException) { + return e.getCause(); + } + return e; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/FallbackDefinitionException.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/FallbackDefinitionException.java new file mode 100644 index 0000000..6d43627 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/FallbackDefinitionException.java @@ -0,0 +1,35 @@ +/** + * 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.contrib.javanica.exception; + + +public class FallbackDefinitionException extends RuntimeException { + + public FallbackDefinitionException() { + } + + public FallbackDefinitionException(String message, Throwable cause) { + super(message, cause); + } + + public FallbackDefinitionException(Throwable cause) { + super(cause); + } + + public FallbackDefinitionException(String message) { + super(message); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/FallbackInvocationException.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/FallbackInvocationException.java new file mode 100644 index 0000000..7149dcd --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/FallbackInvocationException.java @@ -0,0 +1,33 @@ +/** + * Copyright 2012 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.contrib.javanica.exception; + +/** + * Exception specifies error occurred in fallback method. + */ +public class FallbackInvocationException extends RuntimeException { + + public FallbackInvocationException() { + } + + public FallbackInvocationException(String message, Throwable cause) { + super(message, cause); + } + + public FallbackInvocationException(Throwable cause) { + super(cause); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java new file mode 100644 index 0000000..dc53c93 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java @@ -0,0 +1,39 @@ +/** + * Copyright 2012 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.contrib.javanica.exception; + +/** + * Indicates that something is going wrong with cache key generation logic. + * + * @author dmgcodevil + */ +public class HystrixCacheKeyGenerationException extends RuntimeException { + + public HystrixCacheKeyGenerationException() { + } + + public HystrixCacheKeyGenerationException(String message) { + super(message); + } + + public HystrixCacheKeyGenerationException(String message, Throwable cause) { + super(message, cause); + } + + public HystrixCacheKeyGenerationException(Throwable cause) { + super(cause); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java new file mode 100644 index 0000000..0c330a8 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java @@ -0,0 +1,39 @@ +/** + * 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.contrib.javanica.exception; + +/** + * Indicates that something is going wrong with caching logic. + * + * @author dmgcodevil + */ +public class HystrixCachingException extends RuntimeException { + + public HystrixCachingException() { + } + + public HystrixCachingException(String message) { + super(message); + } + + public HystrixCachingException(String message, Throwable cause) { + super(message, cause); + } + + public HystrixCachingException(Throwable cause) { + super(cause); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixPropertyException.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixPropertyException.java new file mode 100644 index 0000000..9e77178 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixPropertyException.java @@ -0,0 +1,33 @@ +/** + * 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.contrib.javanica.exception; + +/** + * Created by dmgcodevil. + */ +public class HystrixPropertyException extends RuntimeException { + + public HystrixPropertyException() { + } + + public HystrixPropertyException(String message, Throwable cause) { + super(message, cause); + } + + public HystrixPropertyException(Throwable cause) { + super(cause); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java new file mode 100644 index 0000000..14d17f6 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java @@ -0,0 +1,203 @@ +/** + * Copyright 2012 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.contrib.javanica.utils; + +import com.google.common.base.Optional; +import com.google.common.base.Throwables; +import org.apache.commons.lang3.Validate; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; + +/** + * Provides common methods to retrieve information from JoinPoint and not only. + */ +public final class AopUtils { + + private AopUtils() { + throw new UnsupportedOperationException("It's prohibited to create instances of the class."); + } + + /** + * Gets a {@link Method} object from target object (not proxy class). + * + * @param joinPoint the {@link JoinPoint} + * @return a {@link Method} object or null if method doesn't exist or if the signature at a join point + * isn't sub-type of {@link MethodSignature} + */ + public static Method getMethodFromTarget(JoinPoint joinPoint) { + Method method = null; + if (joinPoint.getSignature() instanceof MethodSignature) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + method = getDeclaredMethod(joinPoint.getTarget().getClass(), signature.getName(), + getParameterTypes(joinPoint)); + } + return method; + } + + /** + * Gets a {@link Method} object from target object by specified method name. + * + * @param joinPoint the {@link JoinPoint} + * @param methodName the method name + * @return a {@link Method} object or null if method with specified methodName doesn't exist + */ + public static Method getMethodFromTarget(JoinPoint joinPoint, String methodName) { + return getDeclaredMethod(joinPoint.getTarget().getClass(), methodName, + getParameterTypes(joinPoint)); + } + + /** + * Gets parameter types of the join point. + * + * @param joinPoint the join point + * @return the parameter types for the method this object + * represents + */ + public static Class[] getParameterTypes(JoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + return method.getParameterTypes(); + } + + /** + * Gets declared method from specified type by mame and parameters types. + * + * @param type the type + * @param methodName the name of the method + * @param parameterTypes the parameter array + * @return a {@link Method} object or null if method doesn't exist + */ + public static Method getDeclaredMethod(Class type, String methodName, Class... parameterTypes) { + Method method = null; + try { + method = type.getDeclaredMethod(methodName, parameterTypes); + if(method.isBridge()){ + method = MethodProvider.getInstance().unbride(method, type); + } + } catch (NoSuchMethodException e) { + Class superclass = type.getSuperclass(); + if (superclass != null) { + method = getDeclaredMethod(superclass, methodName, parameterTypes); + } + } catch (ClassNotFoundException e) { + Throwables.propagate(e); + } catch (IOException e) { + Throwables.propagate(e); + } + return method; + } + + public static Optional getAnnotation(JoinPoint joinPoint, Class annotation) { + return getAnnotation(joinPoint.getTarget().getClass(), annotation); + } + + public static Optional getAnnotation(Class type, Class annotation) { + Validate.notNull(annotation, "annotation cannot be null"); + Validate.notNull(type, "type cannot be null"); + for (Annotation ann : type.getDeclaredAnnotations()) { + if (ann.annotationType().equals(annotation)) return Optional.of((T) ann); + } + + Class superType = type.getSuperclass(); + if (superType != null && !superType.equals(Object.class)) { + return getAnnotation(superType, annotation); + } + + return Optional.absent(); + } + + public static String getMethodInfo(Method m) { + StringBuilder info = new StringBuilder(); + info.append("Method signature:").append("\n"); + info.append(m.toGenericString()).append("\n"); + + info.append("Declaring class:\n"); + info.append(m.getDeclaringClass().getCanonicalName()).append("\n"); + + info.append("\nFlags:").append("\n"); + info.append("Bridge=").append(m.isBridge()).append("\n"); + info.append("Synthetic=").append(m.isSynthetic()).append("\n"); + info.append("Final=").append(Modifier.isFinal(m.getModifiers())).append("\n"); + info.append("Native=").append(Modifier.isNative(m.getModifiers())).append("\n"); + info.append("Synchronized=").append(Modifier.isSynchronized(m.getModifiers())).append("\n"); + info.append("Abstract=").append(Modifier.isAbstract(m.getModifiers())).append("\n"); + info.append("AccessLevel=").append(getAccessLevel(m.getModifiers())).append("\n"); + + info.append("\nReturn Type: \n"); + info.append("ReturnType=").append(m.getReturnType()).append("\n"); + info.append("GenericReturnType=").append(m.getGenericReturnType()).append("\n"); + + info.append("\nParameters:"); + Class[] pType = m.getParameterTypes(); + Type[] gpType = m.getGenericParameterTypes(); + if (pType.length != 0) { + info.append("\n"); + } else { + info.append("empty\n"); + } + for (int i = 0; i < pType.length; i++) { + info.append("parameter [").append(i).append("]:\n"); + info.append("ParameterType=").append(pType[i]).append("\n"); + info.append("GenericParameterType=").append(gpType[i]).append("\n"); + } + + info.append("\nExceptions:"); + Class[] xType = m.getExceptionTypes(); + Type[] gxType = m.getGenericExceptionTypes(); + if (xType.length != 0) { + info.append("\n"); + } else { + info.append("empty\n"); + } + for (int i = 0; i < xType.length; i++) { + info.append("exception [").append(i).append("]:\n"); + info.append("ExceptionType=").append(xType[i]).append("\n"); + info.append("GenericExceptionType=").append(gxType[i]).append("\n"); + } + + info.append("\nAnnotations:"); + if (m.getAnnotations().length != 0) { + info.append("\n"); + } else { + info.append("empty\n"); + } + + for (int i = 0; i < m.getAnnotations().length; i++) { + info.append("annotation[").append(i).append("]=").append(m.getAnnotations()[i]).append("\n"); + } + + return info.toString(); + } + + private static String getAccessLevel(int modifiers) { + if (Modifier.isPublic(modifiers)) { + return "public"; + } else if (Modifier.isProtected(modifiers)) { + return "protected"; + } else if (Modifier.isPrivate(modifiers)) { + return "private"; + } else { + return "default"; + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/CommonUtils.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/CommonUtils.java new file mode 100644 index 0000000..dafd729 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/CommonUtils.java @@ -0,0 +1,51 @@ +/** + * 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.contrib.javanica.utils; + +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.Arrays; + +/** + * Created by dmgcodevil. + */ +public final class CommonUtils { + + private CommonUtils(){ + + } + + public static Object[] createArgsForFallback(MetaHolder metaHolder, Throwable exception) { + return createArgsForFallback(metaHolder.getArgs(), metaHolder, exception); + } + + public static Object[] createArgsForFallback(Object[] args, MetaHolder metaHolder, Throwable exception) { + if (metaHolder.isExtendedFallback()) { + if (metaHolder.isExtendedParentFallback()) { + args[args.length - 1] = exception; + } else { + args = Arrays.copyOf(args, args.length + 1); + args[args.length - 1] = exception; + } + } else { + if (metaHolder.isExtendedParentFallback()) { + args = ArrayUtils.remove(args, args.length - 1); + } + } + return args; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/EnvUtils.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/EnvUtils.java new file mode 100644 index 0000000..cefef27 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/EnvUtils.java @@ -0,0 +1,48 @@ +/** + * Copyright 2012 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.contrib.javanica.utils; + +import com.netflix.hystrix.contrib.javanica.aop.aspectj.WeavingMode; + +import java.util.Arrays; + +/** + * Created by dmgcodevil + */ +public final class EnvUtils { + + private static final String WEAVING_MODE; + + static { + WEAVING_MODE = System.getProperty("weavingMode", WeavingMode.RUNTIME.name()).toUpperCase(); + } + + private EnvUtils(){ + + } + + public static WeavingMode getWeavingMode() { + try { + return WeavingMode.valueOf(EnvUtils.WEAVING_MODE); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("wrong 'weavingMode' property, supported: " + Arrays.toString(WeavingMode.values()) + ", actual = " + EnvUtils.WEAVING_MODE, e); + } + } + + public static boolean isCompileWeaving() { + return WeavingMode.COMPILE == getWeavingMode(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java new file mode 100644 index 0000000..ebfce24 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java @@ -0,0 +1,428 @@ +/** + * Copyright 2016 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.contrib.javanica.utils; + + +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Supplier; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.command.ExecutionType; +import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import rx.Completable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.flattenTypeVariables; +import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isGenericReturnType; +import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isParametrizedType; +import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isReturnTypeParametrized; +import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isTypeVariable; +import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isWildcardType; + +public class FallbackMethod { + + + private final Method method; + private final boolean extended; + private final boolean defaultFallback; + private ExecutionType executionType; + + public static final FallbackMethod ABSENT = new FallbackMethod(null, false, false); + + public FallbackMethod(Method method) { + this(method, false, false); + } + + public FallbackMethod(Method method, boolean extended, boolean defaultFallback) { + this.method = method; + this.extended = extended; + this.defaultFallback = defaultFallback; + if (method != null) { + this.executionType = ExecutionType.getExecutionType(method.getReturnType()); + } + } + + public boolean isCommand() { + return method.isAnnotationPresent(HystrixCommand.class); + } + + public boolean isPresent() { + return method != null; + } + + public Method getMethod() { + return method; + } + + public ExecutionType getExecutionType() { + return executionType; + } + + public boolean isExtended() { + return extended; + } + + public boolean isDefault() { + return defaultFallback; + } + + public void validateReturnType(Method commandMethod) throws FallbackDefinitionException { + if (isPresent()) { + Class commandReturnType = commandMethod.getReturnType(); + if (ExecutionType.OBSERVABLE == ExecutionType.getExecutionType(commandReturnType)) { + if (ExecutionType.OBSERVABLE != getExecutionType()) { + Type commandParametrizedType = commandMethod.getGenericReturnType(); + + // basically any object can be wrapped into Completable, Completable itself ins't parametrized + if(Completable.class.isAssignableFrom(commandMethod.getReturnType())) { + validateCompletableReturnType(commandMethod, method.getReturnType()); + return; + } + + if (isReturnTypeParametrized(commandMethod)) { + commandParametrizedType = getFirstParametrizedType(commandMethod); + } + validateParametrizedType(commandParametrizedType, method.getGenericReturnType(), commandMethod, method); + } else { + validateReturnType(commandMethod, method); + } + + + } else if (ExecutionType.ASYNCHRONOUS == ExecutionType.getExecutionType(commandReturnType)) { + if (isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) { + validateReturnType(commandMethod, method); + } + if (ExecutionType.ASYNCHRONOUS != getExecutionType()) { + Type commandParametrizedType = commandMethod.getGenericReturnType(); + if (isReturnTypeParametrized(commandMethod)) { + commandParametrizedType = getFirstParametrizedType(commandMethod); + } + validateParametrizedType(commandParametrizedType, method.getGenericReturnType(), commandMethod, method); + } + if (!isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Future if the fallback isn't command when the command is async.")); + } + } else { + if (ExecutionType.ASYNCHRONOUS == getExecutionType()) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Future if command isn't asynchronous.")); + } + if (ExecutionType.OBSERVABLE == getExecutionType()) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Observable if command isn't observable.")); + } + validateReturnType(commandMethod, method); + } + + } + } + + private Type getFirstParametrizedType(Method m) { + Type gtype = m.getGenericReturnType(); + if (gtype instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) gtype; + return pType.getActualTypeArguments()[0]; + } + return null; + } + + // everything can be wrapped into completable except 'void' + private void validateCompletableReturnType(Method commandMethod, Class callbackReturnType) { + if (Void.TYPE == callbackReturnType) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return 'void' if command return type is " + Completable.class.getSimpleName())); + } + } + + private void validateReturnType(Method commandMethod, Method fallbackMethod) { + if (isGenericReturnType(commandMethod)) { + List commandParametrizedTypes = flattenTypeVariables(commandMethod.getGenericReturnType()); + List fallbackParametrizedTypes = flattenTypeVariables(fallbackMethod.getGenericReturnType()); + Result result = equalsParametrizedTypes(commandParametrizedTypes, fallbackParametrizedTypes); + if (!result.success) { + List msg = new ArrayList(); + for (Error error : result.errors) { + Optional parentKindOpt = getParentKind(error.commandType, commandParametrizedTypes); + String extraHint = ""; + if (parentKindOpt.isPresent()) { + Type parentKind = parentKindOpt.get(); + if (isParametrizedType(parentKind)) { + extraHint = "--> " + ((ParameterizedType) parentKind).getRawType().toString() + "\n"; + } + } + msg.add(String.format(error.reason + "\n" + extraHint + "Command type literal pos: %s; Fallback type literal pos: %s", + positionAsString(error.commandType, commandParametrizedTypes), + positionAsString(error.fallbackType, fallbackParametrizedTypes))); + } + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, StringUtils.join(msg, "\n"))); + } + } + validatePlainReturnType(commandMethod, fallbackMethod); + } + + private void validatePlainReturnType(Method commandMethod, Method fallbackMethod) { + validatePlainReturnType(commandMethod.getReturnType(), fallbackMethod.getReturnType(), commandMethod, fallbackMethod); + } + + private void validatePlainReturnType(Class commandReturnType, Class fallbackReturnType, Method commandMethod, Method fallbackMethod) { + if (!commandReturnType.isAssignableFrom(fallbackReturnType)) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, fallbackMethod, "Fallback method '" + + fallbackMethod + "' must return: " + commandReturnType + " or its subclass")); + } + } + + private void validateParametrizedType(Type commandReturnType, Type fallbackReturnType, Method commandMethod, Method fallbackMethod) { + if (!commandReturnType.equals(fallbackReturnType)) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, fallbackMethod, "Fallback method '" + + fallbackMethod + "' must return: " + commandReturnType + " or its subclass")); + } + } + + private String createErrorMsg(Method commandMethod, Method fallbackMethod, String hint) { + return "Incompatible return types. \nCommand method: " + commandMethod + ";\nFallback method: " + fallbackMethod + ";\n" + + (StringUtils.isNotBlank(hint) ? "Hint: " + hint : ""); + } + + private static final Result SUCCESS = Result.success(); + + private Result equalsParametrizedTypes(List commandParametrizedTypes, List fallbackParametrizedTypes) { + if (commandParametrizedTypes.size() != fallbackParametrizedTypes.size()) { + return Result.failure(Collections.singletonList( + new Error("Different size of types variables.\n" + + "Command type literals size = " + commandParametrizedTypes.size() + ": " + commandParametrizedTypes + "\n" + + "Fallback type literals size = " + fallbackParametrizedTypes.size() + ": " + fallbackParametrizedTypes + "\n" + ))); + } + + for (int i = 0; i < commandParametrizedTypes.size(); i++) { + Type commandParametrizedType = commandParametrizedTypes.get(i); + Type fallbackParametrizedType = fallbackParametrizedTypes.get(i); + Result result = equals(commandParametrizedType, fallbackParametrizedType); + if (!result.success) return result; + } + + return SUCCESS; + } + + // Regular Type#equals method cannot be used to compare parametrized types and type variables + // because it compares generic declarations, see java.lang.reflect.GenericDeclaration. + // If generic declaration is an instance of java.lang.reflect.Method then command and fallback return types have with different generic declarations which aren't the same. + // In this case we need to compare only few type properties, such as bounds for type literal and row types for parametrized types. + private static Result equals(Type commandType, Type fallbackType) { + if (isParametrizedType(commandType) && isParametrizedType(fallbackType)) { + final ParameterizedType pt1 = (ParameterizedType) commandType; + final ParameterizedType pt2 = (ParameterizedType) fallbackType; + Result result = regularEquals(pt1.getRawType(), pt2.getRawType()); + return result.andThen(new Supplier() { + @Override + public Result get() { + return FallbackMethod.equals(pt1.getActualTypeArguments(), pt2.getActualTypeArguments()); + } + }); + } else if (isTypeVariable(commandType) && isTypeVariable(fallbackType)) { + final TypeVariable tv1 = (TypeVariable) commandType; + final TypeVariable tv2 = (TypeVariable) fallbackType; + if (tv1.getGenericDeclaration() instanceof Method && tv2.getGenericDeclaration() instanceof Method) { + Result result = equals(tv1.getBounds(), tv2.getBounds()); + return result.append(new Supplier>() { + @Override + public List get() { + return Collections.singletonList(boundsError(tv1, tv1.getBounds(), "", tv2, tv2.getBounds())); + } + }); + } + return regularEquals(tv1, tv2); + } else if (isWildcardType(commandType) && isWildcardType(fallbackType)) { + final WildcardType wt1 = (WildcardType) commandType; + final WildcardType wt2 = (WildcardType) fallbackType; + Result result = equals(wt1.getLowerBounds(), wt2.getLowerBounds()); + result = result.append(new Supplier>() { + @Override + public List get() { + return Collections.singletonList(boundsError(wt1, wt1.getLowerBounds(), "lower", wt2, wt2.getLowerBounds())); + } + }); + + if (result.isFailure()) return result; + + result = equals(wt1.getUpperBounds(), wt2.getUpperBounds()); + return result.append(new Supplier>() { + @Override + public List get() { + return Collections.singletonList(boundsError(wt1, wt1.getUpperBounds(), "upper", wt2, wt2.getUpperBounds())); + } + }); + } else { + return regularEquals(commandType, fallbackType); + } + } + + private static Result regularEquals(final Type commandType, final Type fallbackType) { + return Result.of(Objects.equal(commandType, fallbackType), new Supplier>() { + @Override + public List get() { + return Collections.singletonList(new Error( + commandType, + String.format("Different types. Command type: '%s'; fallback type: '%s'", commandType, fallbackType), + fallbackType)); + } + }); + } + + private static Optional getParentKind(Type type, List types) { + int pos = position(type, types); + if (pos <= 0) return Optional.absent(); + return Optional.of(types.get(pos - 1)); + } + + private static String positionAsString(Type type, List types) { + int pos = position(type, types); + if (pos < 0) { + return "unknown"; + } + return String.valueOf(pos); + } + + private static int position(Type type, List types) { + if (type == null) return -1; + if (types == null || types.isEmpty()) return -1; + return types.indexOf(type); + } + + private static Error boundsError(Type t1, Type[] b1, String boundType, Type t2, Type[] b2) { + return new Error(t1, + String.format("Different %s bounds. Command bounds: '%s'; Fallback bounds: '%s'", + boundType, + StringUtils.join(b1, ", "), + StringUtils.join(b2, ", ")), + t2); + } + + private static Result equals(Type[] t1, Type[] t2) { + if (t1 == null && t2 == null) return SUCCESS; + if (t1 == null) return Result.failure(); + if (t2 == null) return Result.failure(); + if (t1.length != t2.length) + return Result.failure(new Error(String.format("Different size of type literals. Command size = %d, fallback size = %d", + t1.length, t2.length))); + Result result = SUCCESS; + for (int i = 0; i < t1.length; i++) { + result = result.combine(equals(t1[i], t2[i])); + if (result.isFailure()) return result; + } + return result; + } + + private static class Result { + boolean success; + List errors = Collections.emptyList(); + + boolean isSuccess() { + return success; + } + + boolean isFailure() { + return !success; + } + + static Result of(boolean res, Supplier> errors) { + if (res) return success(); + return failure(errors.get()); + } + + static Result success() { + return new Result(true); + } + + static Result failure() { + return new Result(false); + } + + static Result failure(Error... errors) { + return new Result(false, Arrays.asList(errors)); + } + + static Result failure(List errors) { + return new Result(false, errors); + } + + Result combine(Result r) { + return new Result(this.success && r.success, merge(this.errors, r.errors)); + } + + Result andThen(Supplier resultSupplier) { + if (!success) return this; + return resultSupplier.get(); + } + + Result append(List errors) { + if (success) return this; + return failure(merge(this.errors, errors)); + } + + Result append(Supplier> errors) { + if (success) return this; + return append(errors.get()); + } + + static List merge(@Nonnull List e1, @Nonnull List e2) { + List res = new ArrayList(e1.size() + e2.size()); + res.addAll(e1); + res.addAll(e2); + return Collections.unmodifiableList(res); + } + + Result(boolean success, List errors) { + Validate.notNull(errors, "errors cannot be null"); + this.success = success; + this.errors = errors; + } + + Result(boolean success) { + this.success = success; + this.errors = Collections.emptyList(); + } + } + + private static class Error { + @Nullable + Type commandType; + String reason; + @Nullable + Type fallbackType; + + Error(String reason) { + this.reason = reason; + } + + Error(Type commandType, String reason, Type fallbackType) { + this.commandType = commandType; + this.reason = reason; + this.fallbackType = fallbackType; + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FutureDecorator.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FutureDecorator.java new file mode 100644 index 0000000..1544945 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FutureDecorator.java @@ -0,0 +1,64 @@ +/** + * Copyright 2016 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.contrib.javanica.utils; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +public class FutureDecorator implements Future { + + private Future origin; + + public FutureDecorator(Future origin) { + this.origin = origin; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return origin.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return origin.isCancelled(); + } + + @Override + public boolean isDone() { + return origin.isDone(); + } + + @Override + public Object get() throws InterruptedException, ExecutionException { + Object result = origin.get(); + if (result instanceof Future) { + return ((Future) result).get(); + } + return result; + } + + @Override + public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + Object result = origin.get(timeout, unit); + if (result instanceof Future) { + return ((Future) result).get(); + } + return result; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/MethodProvider.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/MethodProvider.java new file mode 100644 index 0000000..c5dbea2 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/MethodProvider.java @@ -0,0 +1,305 @@ +/** + * 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.contrib.javanica.utils; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static org.objectweb.asm.Opcodes.ACC_BRIDGE; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; +import static org.objectweb.asm.Opcodes.ASM5; + +/** + * Created by dmgcodevil + */ +public final class MethodProvider { + + private MethodProvider() { + + } + + private static final MethodProvider INSTANCE = new MethodProvider(); + + public static MethodProvider getInstance() { + return INSTANCE; + } + + private static final FallbackMethodFinder FALLBACK_METHOD_FINDER = new SpecificFallback(new DefaultCallback()); + + private Map cache = new ConcurrentHashMap(); + + public FallbackMethod getFallbackMethod(Class type, Method commandMethod) { + return getFallbackMethod(type, commandMethod, false); + } + + /** + * Gets fallback method for command method. + * + * @param enclosingType the enclosing class + * @param commandMethod the command method. in the essence it can be a fallback + * method annotated with HystrixCommand annotation that has a fallback as well. + * @param extended true if the given commandMethod was derived using additional parameter, otherwise - false + * @return new instance of {@link FallbackMethod} or {@link FallbackMethod#ABSENT} if there is no suitable fallback method for the given command + */ + public FallbackMethod getFallbackMethod(Class enclosingType, Method commandMethod, boolean extended) { + if (commandMethod.isAnnotationPresent(HystrixCommand.class)) { + return FALLBACK_METHOD_FINDER.find(enclosingType, commandMethod, extended); + } + return FallbackMethod.ABSENT; + } + + private void getDefaultFallback(){ + + } + + private String getClassLevelFallback(Class enclosingClass) { + if (enclosingClass.isAnnotationPresent(DefaultProperties.class)) { + return enclosingClass.getAnnotation(DefaultProperties.class).defaultFallback(); + } + return StringUtils.EMPTY; + } + + private static class SpecificFallback extends FallbackMethodFinder { + + public SpecificFallback(FallbackMethodFinder next) { + super(next); + } + + @Override + boolean isSpecific() { + return true; + } + + @Override + public String getFallbackName(Class enclosingType, Method commandMethod) { + return commandMethod.getAnnotation(HystrixCommand.class).fallbackMethod(); + } + + @Override + boolean canHandle(Class enclosingType, Method commandMethod) { + return StringUtils.isNotBlank(getFallbackName(enclosingType, commandMethod)); + } + } + + private static class DefaultCallback extends FallbackMethodFinder { + @Override + boolean isDefault() { + return true; + } + + @Override + public String getFallbackName(Class enclosingType, Method commandMethod) { + String commandDefaultFallback = commandMethod.getAnnotation(HystrixCommand.class).defaultFallback(); + String classDefaultFallback = Optional.fromNullable(enclosingType.getAnnotation(DefaultProperties.class)) + .transform(new Function() { + @Override + public String apply(DefaultProperties input) { + return input.defaultFallback(); + } + }).or(StringUtils.EMPTY); + + return StringUtils.defaultIfEmpty(commandDefaultFallback, classDefaultFallback); + } + + @Override + boolean canHandle(Class enclosingType, Method commandMethod) { + return StringUtils.isNotBlank(getFallbackName(enclosingType, commandMethod)); + } + } + + private static abstract class FallbackMethodFinder { + FallbackMethodFinder next; + + public FallbackMethodFinder() { + } + + public FallbackMethodFinder(FallbackMethodFinder next) { + this.next = next; + } + + boolean isDefault() { + return false; + } + + boolean isSpecific(){ + return false; + } + + public abstract String getFallbackName(Class enclosingType, Method commandMethod); + + public FallbackMethod find(Class enclosingType, Method commandMethod, boolean extended) { + if (canHandle(enclosingType, commandMethod)) { + return doFind(enclosingType, commandMethod, extended); + } else if (next != null) { + return next.find(enclosingType, commandMethod, extended); + } else { + return FallbackMethod.ABSENT; + } + } + + abstract boolean canHandle(Class enclosingType, Method commandMethod); + + private FallbackMethod doFind(Class enclosingType, Method commandMethod, boolean extended) { + String name = getFallbackName(enclosingType, commandMethod); + Class[] fallbackParameterTypes = null; + if (isDefault()) { + fallbackParameterTypes = new Class[0]; + } else { + fallbackParameterTypes = commandMethod.getParameterTypes(); + } + + if (extended && fallbackParameterTypes[fallbackParameterTypes.length - 1] == Throwable.class) { + fallbackParameterTypes = ArrayUtils.remove(fallbackParameterTypes, fallbackParameterTypes.length - 1); + } + + Class[] extendedFallbackParameterTypes = Arrays.copyOf(fallbackParameterTypes, + fallbackParameterTypes.length + 1); + extendedFallbackParameterTypes[fallbackParameterTypes.length] = Throwable.class; + + Optional exFallbackMethod = getMethod(enclosingType, name, extendedFallbackParameterTypes); + Optional fMethod = getMethod(enclosingType, name, fallbackParameterTypes); + Method method = exFallbackMethod.or(fMethod).orNull(); + if (method == null) { + throw new FallbackDefinitionException("fallback method wasn't found: " + name + "(" + Arrays.toString(fallbackParameterTypes) + ")"); + } + return new FallbackMethod(method, exFallbackMethod.isPresent(), isDefault()); + } + + } + + + /** + * Gets method by name and parameters types using reflection, + * if the given type doesn't contain required method then continue applying this method for all super classes up to Object class. + * + * @param type the type to search method + * @param name the method name + * @param parameterTypes the parameters types + * @return Some if method exists otherwise None + */ + public static Optional getMethod(Class type, String name, Class... parameterTypes) { + Method[] methods = type.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals(name) && Arrays.equals(method.getParameterTypes(), parameterTypes)) { + return Optional.of(method); + } + } + Class superClass = type.getSuperclass(); + if (superClass != null && !superClass.equals(Object.class)) { + return getMethod(superClass, name, parameterTypes); + } else { + return Optional.absent(); + } + } + + /** + * Finds generic method for the given bridge method. + * + * @param bridgeMethod the bridge method + * @param aClass the type where the bridge method is declared + * @return generic method + * @throws IOException + * @throws NoSuchMethodException + * @throws ClassNotFoundException + */ + public Method unbride(final Method bridgeMethod, Class aClass) throws IOException, NoSuchMethodException, ClassNotFoundException { + if (bridgeMethod.isBridge() && bridgeMethod.isSynthetic()) { + if (cache.containsKey(bridgeMethod)) { + return cache.get(bridgeMethod); + } + + ClassReader classReader = new ClassReader(aClass.getName()); + final MethodSignature methodSignature = new MethodSignature(); + classReader.accept(new ClassVisitor(ASM5) { + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + boolean bridge = (access & ACC_BRIDGE) != 0 && (access & ACC_SYNTHETIC) != 0; + if (bridge && bridgeMethod.getName().equals(name) && getParameterCount(desc) == bridgeMethod.getParameterTypes().length) { + return new MethodFinder(methodSignature); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + }, 0); + Method method = aClass.getDeclaredMethod(methodSignature.name, methodSignature.getParameterTypes()); + cache.put(bridgeMethod, method); + return method; + + } else { + return bridgeMethod; + } + } + + private static int getParameterCount(String desc) { + return parseParams(desc).length; + } + + private static String[] parseParams(String desc) { + String params = desc.split("\\)")[0].replace("(", ""); + if (params.length() == 0) { + return new String[0]; + } + return params.split(";"); + } + + private static class MethodSignature { + String name; + String desc; + + public Class[] getParameterTypes() throws ClassNotFoundException { + if (desc == null) { + return new Class[0]; + } + String[] params = parseParams(desc); + Class[] parameterTypes = new Class[params.length]; + + for (int i = 0; i < params.length; i++) { + String arg = params[i].substring(1).replace("/", "."); + parameterTypes[i] = Class.forName(arg); + } + return parameterTypes; + } + } + + private static class MethodFinder extends MethodVisitor { + private MethodSignature methodSignature; + + public MethodFinder(MethodSignature methodSignature) { + super(ASM5); + this.methodSignature = methodSignature; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + methodSignature.name = name; + methodSignature.desc = desc; + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/TypeHelper.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/TypeHelper.java new file mode 100644 index 0000000..006591a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/TypeHelper.java @@ -0,0 +1,105 @@ +/** + * 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.contrib.javanica.utils; + +import com.google.common.collect.TreeTraverser; +import org.apache.commons.lang3.Validate; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Helper class that provides convenient methods to work with java types. + *

+ * Created by dmgcodevil. + */ +public final class TypeHelper { + private TypeHelper() { + } + + + public static boolean isGenericReturnType(Method method) { + return isParametrizedType(method.getGenericReturnType()) || isTypeVariable(method.getGenericReturnType()); + } + + /** + * Check whether return type of the given method is parametrized or not. + * + * @param method the method + * @return true - if return type is {@link ParameterizedType}, otherwise - false + */ + public static boolean isReturnTypeParametrized(Method method) { + return isParametrizedType(method.getGenericReturnType()); + } + + public static boolean isParametrizedType(Type t) { + return t instanceof ParameterizedType; + } + + public static boolean isTypeVariable(Type t) { + return t instanceof TypeVariable; + } + + public static boolean isWildcardType(Type t) { + return t instanceof WildcardType; + } + + /** + * Unwinds parametrized type into plain list that contains all parameters for the given type including nested parameterized types, + * for example calling the method for the following type + * + * GType>, Parent>>> + * + * will return list of 8 elements: + * + * [GType, GType, GDoubleType, GType, GDoubleType, Parent, Parent, Parent] + * + * if the given type is not parametrized then returns list with one element which is given type passed into method. + * + * @param type the parameterized type + * @return list of {@link Type} + */ + @ParametersAreNonnullByDefault + public static List flattenTypeVariables(Type type) { + Validate.notNull(type, "type cannot be null"); + List types = new ArrayList(); + TreeTraverser typeTraverser = new TreeTraverser() { + @Override + public Iterable children(Type root) { + if (root instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) root; + return Arrays.asList(pType.getActualTypeArguments()); + } else if (root instanceof TypeVariable) { + TypeVariable pType = (TypeVariable) root; + return Arrays.asList(pType.getBounds()); + } + return Collections.emptyList(); + } + }; + for (Type t : typeTraverser.breadthFirstTraversal(type)) { + types.add(t); + } + return types; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/ajc/AjcUtils.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/ajc/AjcUtils.java new file mode 100644 index 0000000..ad18013 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/ajc/AjcUtils.java @@ -0,0 +1,109 @@ +/** + * 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.contrib.javanica.utils.ajc; + +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import org.aspectj.lang.reflect.MethodSignature; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Created by dmgcodevil + */ +public final class AjcUtils { + + private AjcUtils() { + throw new UnsupportedOperationException("it's prohibited to create instances of this class"); + } + + + public static Method getAjcMethod(final Class target, final String methodName, final AdviceType adviceType, final Class... pTypes) { + for (Method method : target.getDeclaredMethods()) { + if (method.getName().startsWith(methodName + adviceType.getPostfix()) + && Modifier.isFinal(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) { + Class[] parameterTypes = method.getParameterTypes(); + if (pTypes.length == 0 && parameterTypes.length == 0) { + return method; + } + if (pTypes.length == parameterTypes.length - 2) { + boolean match = true; + Class[] origParamTypes = removeAspectjArgs(parameterTypes); + int index = 0; + for (Class pType : origParamTypes) { + Class expected = pTypes[index++]; + if (pType != expected) { + match = false; + } + } + if (match) { + return method; + } + } + } + } + if (target.getSuperclass() != null) { + return getAjcMethod(target.getSuperclass(), methodName, adviceType, pTypes); + } + + return null; + } + + public static Method getAjcMethodAroundAdvice(final Class target, final String methodName, final Class... pTypes) { + return getAjcMethod(target, methodName, AdviceType.Around, pTypes); + } + + + public static Method getAjcMethodAroundAdvice(Class target, MethodSignature signature) { + return getAjcMethodAroundAdvice(target, signature.getMethod().getName(), signature.getParameterTypes()); + } + + + public static Method getAjcMethodAroundAdvice(Class target, Method method) { + return getAjcMethodAroundAdvice(target, method.getName(), method.getParameterTypes()); + } + + + public static Object invokeAjcMethod(Method method, Object target, MetaHolder metaHolder, Object... args) throws InvocationTargetException, IllegalAccessException { + method.setAccessible(true); + Object[] extArgs = new Object[args.length + 2]; + extArgs[0] = target; + System.arraycopy(args, 0, extArgs, 1, args.length); + extArgs[extArgs.length - 1] = metaHolder.getJoinPoint(); + return method.invoke(target, extArgs); + } + + private static Class[] removeAspectjArgs(Class[] parameterTypes) { + Class[] origParamTypes = new Class[parameterTypes.length - 2]; + System.arraycopy(parameterTypes, 1, origParamTypes, 0, parameterTypes.length - 2); + return origParamTypes; + } + + public enum AdviceType { + Around("_aroundBody"); + private String postfix; + + AdviceType(String postfix) { + this.postfix = postfix; + } + + public String getPostfix() { + return postfix; + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/resources/dummy.txt b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/resources/dummy.txt new file mode 100644 index 0000000..936ad77 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/main/resources/dummy.txt @@ -0,0 +1,16 @@ +==== + Copyright 2016 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. +==== + diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactoryTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactoryTest.java new file mode 100644 index 0000000..f902625 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactoryTest.java @@ -0,0 +1,160 @@ +/** + * 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.contrib.javanica.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCachingException; +import org.junit.Test; + + +import java.lang.annotation.Annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Unit test for {@link CacheInvocationContextFactory}. + * + * @author dmgcodevil + */ +public class CacheInvocationContextFactoryTest { + + @Test + public void testCreateCacheResultInvocationContext_givenMethodAnnotatedWithCacheResult_shouldCreateCorrectCacheKeyInvocationContext() + throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + String param2 = "val_2"; + Integer param3 = 3; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, String.class, Integer.class)) + .args(new Object[]{param1, param2, param3}) + .obj(testCacheClass).build(); + // when + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + + // then + assertNotNull(context.getKeyParameters()); + assertEquals(2, context.getKeyParameters().size()); + assertEquals(String.class, context.getKeyParameters().get(0).getRawType()); + assertEquals(0, context.getKeyParameters().get(0).getPosition()); + assertEquals(param1, context.getKeyParameters().get(0).getValue()); + assertTrue(isAnnotationPresent(context.getKeyParameters().get(0), CacheKey.class)); + + assertEquals(Integer.class, context.getKeyParameters().get(1).getRawType()); + assertEquals(2, context.getKeyParameters().get(1).getPosition()); + assertEquals(param3, context.getKeyParameters().get(1).getValue()); + assertTrue(isAnnotationPresent(context.getKeyParameters().get(1), CacheKey.class)); + } + + @Test + public void testCreateCacheRemoveInvocationContext_givenMethodAnnotatedWithCacheRemove_shouldCreateCorrectCacheKeyInvocationContext() + throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheRemoveMethod", String.class)) + .args(new Object[]{param1}) + .obj(testCacheClass).build(); + // when + CacheInvocationContext context = CacheInvocationContextFactory.createCacheRemoveInvocationContext(metaHolder); + + // then + assertNotNull(context.getKeyParameters()); + assertEquals(1, context.getKeyParameters().size()); + CacheInvocationParameter actual = context.getKeyParameters().get(0); + assertEquals(String.class, actual.getRawType()); + assertEquals(param1, actual.getValue()); + assertEquals(0, actual.getPosition()); + } + + @Test(expected = HystrixCachingException.class) + public void testCacheResultMethodWithWrongCacheKeyMethodSignature_givenWrongCacheKeyMethod_shouldThrowException() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethodWithWrongCacheKeyMethodSignature", String.class)) + .args(new Object[]{param1}) + .obj(testCacheClass).build(); + // when + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + // then expected HystrixCachingException + } + + @Test(expected = HystrixCachingException.class) + public void testCacheResultMethodWithCacheKeyMethodWithWrongReturnType_givenCacheKeyMethodWithWrongReturnType_shouldThrowException() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethodWithCacheKeyMethodWithWrongReturnType", String.class, String.class)) + .args(new Object[]{param1}) + .obj(testCacheClass).build(); + // when + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + System.out.println(context); + // then expected HystrixCachingException + } + + public static class TestCacheClass { + + @CacheResult + public Object cacheResultMethod(@CacheKey String param1, String param2, @CacheKey Integer param3) { + return null; + } + + @CacheRemove(commandKey = "test") + public Object cacheRemoveMethod(String param1) { + return null; + } + + @CacheResult(cacheKeyMethod = "cacheKeyMethodSignature") + public Object cacheResultMethodWithWrongCacheKeyMethodSignature(String param2) { + return null; + } + + private String cacheKeyMethodSignature(String param1, String param2) { + return null; + } + + @CacheResult(cacheKeyMethod = "cacheKeyMethodWithWrongReturnType") + public Object cacheResultMethodWithCacheKeyMethodWithWrongReturnType(String param1, String param2) { + return null; + } + + private Long cacheKeyMethodWithWrongReturnType(String param1, String param2) { + return null; + } + } + + private static boolean isAnnotationPresent(CacheInvocationParameter parameter, final Class annotation) { + return Iterables.tryFind(parameter.getAnnotations(), new Predicate() { + @Override + public boolean apply(Annotation input) { + return input.annotationType().equals(annotation); + } + }).isPresent(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterTest.java new file mode 100644 index 0000000..dba4118 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterTest.java @@ -0,0 +1,60 @@ +/** + * 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.contrib.javanica.cache; + + +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class CacheInvocationParameterTest { + + @Test + public void testCacheInvocationParameterConstructor() throws NoSuchMethodException { + // given + Class rawType = String.class; + Object value = "test"; + Method method = CacheInvocationParameterTest.class.getDeclaredMethod("stabMethod", String.class); + method.setAccessible(true); + Annotation[] annotations = method.getParameterAnnotations()[0]; + int position = 0; + // when + CacheInvocationParameter cacheInvocationParameter = new CacheInvocationParameter(rawType, value, annotations, position); + // then + assertEquals(rawType, cacheInvocationParameter.getRawType()); + assertEquals(value, cacheInvocationParameter.getValue()); + assertEquals(annotations[0], cacheInvocationParameter.getCacheKeyAnnotation()); + assertTrue(cacheInvocationParameter.hasCacheKeyAnnotation()); + assertTrue(cacheInvocationParameter.getAnnotations().contains(annotations[0])); + + try { + cacheInvocationParameter.getAnnotations().clear(); + fail(); + } catch (Throwable e) { + // getAnnotations should return immutable set. + } + } + + private static void stabMethod(@CacheKey String val) { + + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGeneratorTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGeneratorTest.java new file mode 100644 index 0000000..9d9f435 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGeneratorTest.java @@ -0,0 +1,140 @@ +/** + * 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.contrib.javanica.cache; + + +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class HystrixCacheKeyGeneratorTest { + + @Test + public void testGenerateCacheKey_givenUser_shouldReturnCorrectCacheKey() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String id = "1"; + User user = new User(); + user.setId(id); + Profile profile = new Profile("user name"); + user.setProfile(profile); + String expectedKey = id + user.getProfile().getName(); + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, User.class)) + .args(new Object[]{id, user}) + .obj(testCacheClass).build(); + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + HystrixCacheKeyGenerator keyGenerator = HystrixCacheKeyGenerator.getInstance(); + // when + String actual = keyGenerator.generateCacheKey(context).getCacheKey(); + // then + assertEquals(expectedKey, actual); + } + + @Test + public void testGenerateCacheKey_givenUserWithNullProfile_shouldReturnCorrectCacheKey() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String id = "1"; + User user = new User(); + user.setId(id); + user.setProfile(null); + String expectedKey = id; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, User.class)) + .args(new Object[]{id, user}) + .obj(testCacheClass).build(); + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + HystrixCacheKeyGenerator keyGenerator = HystrixCacheKeyGenerator.getInstance(); + // when + String actual = keyGenerator.generateCacheKey(context).getCacheKey(); + // then + assertEquals(expectedKey, actual); + } + + @Test + public void testGenerateCacheKey_givenCacheKeyMethodWithNoArguments_shouldReturnEmptyCacheKey() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod")) + .args(new Object[]{}) + .obj(testCacheClass).build(); + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + HystrixCacheKeyGenerator keyGenerator = HystrixCacheKeyGenerator.getInstance(); + // when + HystrixGeneratedCacheKey actual = keyGenerator.generateCacheKey(context); + // then + assertEquals(DefaultHystrixGeneratedCacheKey.EMPTY, actual); + } + + public static class TestCacheClass { + + @CacheResult + public Object cacheResultMethod(@CacheKey String id, @CacheKey("profile.name") User user) { + return "test"; + } + + @CacheResult + public Object cacheResultMethod() { + return "test"; + } + + } + + public static class User { + private String id; + private Profile profile; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Profile getProfile() { + return profile; + } + + public void setProfile(Profile profile) { + this.profile = profile; + } + } + + public static class Profile { + private String name; + + public Profile() { + } + + public Profile(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/command/ExecutionTypeTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/command/ExecutionTypeTest.java new file mode 100644 index 0000000..e0bf815 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/command/ExecutionTypeTest.java @@ -0,0 +1,73 @@ +/** + * 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.contrib.javanica.command; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import rx.Observable; +import rx.internal.operators.OperatorMulticast; + +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.RunnableFuture; + +import static com.netflix.hystrix.contrib.javanica.command.ExecutionType.ASYNCHRONOUS; +import static com.netflix.hystrix.contrib.javanica.command.ExecutionType.OBSERVABLE; +import static com.netflix.hystrix.contrib.javanica.command.ExecutionType.SYNCHRONOUS; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +@RunWith(Parameterized.class) +public class ExecutionTypeTest { + + @Parameterized.Parameters + public static List data() { + return asList(new Object[][]{ + {returnType(Integer.class), shouldHaveExecutionType(SYNCHRONOUS)}, + {returnType(List.class), shouldHaveExecutionType(SYNCHRONOUS)}, + {returnType(Object.class), shouldHaveExecutionType(SYNCHRONOUS)}, + {returnType(Class.class), shouldHaveExecutionType(SYNCHRONOUS)}, + {returnType(Future.class), shouldHaveExecutionType(ASYNCHRONOUS)}, + {returnType(AsyncResult.class), shouldHaveExecutionType(ASYNCHRONOUS)}, + {returnType(RunnableFuture.class), shouldHaveExecutionType(ASYNCHRONOUS)}, + {returnType(Observable.class), shouldHaveExecutionType(OBSERVABLE)}, + {returnType(OperatorMulticast.class), shouldHaveExecutionType(OBSERVABLE)}, + }); + } + + @Test + public void should_return_correct_execution_type() throws Exception { + assertEquals("Unexpected execution type for method return type: " + methodReturnType, expectedType, ExecutionType.getExecutionType(methodReturnType)); + + } + + private static ExecutionType shouldHaveExecutionType(final ExecutionType type) { + return type; + } + + private static Class returnType(final Class aClass) { + return aClass; + } + + private final Class methodReturnType; + private final ExecutionType expectedType; + + public ExecutionTypeTest(final Class methodReturnType, final ExecutionType expectedType) { + this.methodReturnType = methodReturnType; + this.expectedType = expectedType; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/BasicHystrixTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/BasicHystrixTest.java new file mode 100644 index 0000000..45f4e97 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/BasicHystrixTest.java @@ -0,0 +1,59 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common; + +import com.google.common.base.Throwables; +import com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.Rule; + +import java.lang.reflect.Field; + +/** + * Created by dmgcodevil + */ +public abstract class BasicHystrixTest { + @Rule + public HystrixRequestContextRule request = new HystrixRequestContextRule(); + + protected final HystrixRequestContext getHystrixContext() { + return request.context(); + } + + protected void resetContext() { + request.reset(); + } + + protected final HystrixThreadPoolProperties getThreadPoolProperties(HystrixInvokableInfo command) { + try { + Field field = command.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("threadPool"); + field.setAccessible(true); + HystrixThreadPool threadPool = (HystrixThreadPool) field.get(command); + + Field field2 = HystrixThreadPool.HystrixThreadPoolDefault.class.getDeclaredField("properties"); + field2.setAccessible(true); + return (HystrixThreadPoolProperties) field2.get(threadPool); + + } catch (NoSuchFieldException e) { + throw Throwables.propagate(e); + } catch (IllegalAccessException e) { + throw Throwables.propagate(e); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/CommonUtils.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/CommonUtils.java new file mode 100644 index 0000000..02ea9cf --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/CommonUtils.java @@ -0,0 +1,88 @@ +/** + * 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.contrib.javanica.test.common; + + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertTrue; + +public class CommonUtils { + + public HystrixCommandMetrics getMetrics(String commandKey) { + return HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(commandKey)); + } + + + public static HystrixInvokableInfo getLastExecutedCommand() { + Collection> executedCommands = + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands(); + return Iterables.getLast(executedCommands); + } + + public static void assertExecutedCommands(String... commands) { + Collection> executedCommands = + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands(); + + List executedCommandsKeys = getExecutedCommandsKeys(Lists.newArrayList(executedCommands)); + + for (String cmd : commands) { + assertTrue("command: '" + cmd + "' wasn't executed", executedCommandsKeys.contains(cmd)); + } + } + + public static List getExecutedCommandsKeys() { + Collection> executedCommands = + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands(); + + return getExecutedCommandsKeys(Lists.newArrayList(executedCommands)); + } + + public static List getExecutedCommandsKeys(List> executedCommands) { + return Lists.transform(executedCommands, new Function, String>() { + @Nullable + @Override + public String apply(@Nullable HystrixInvokableInfo input) { + return input.getCommandKey().name(); + } + }); + } + + public static HystrixInvokableInfo getHystrixCommandByKey(String key) { + HystrixInvokableInfo hystrixCommand = null; + Collection> executedCommands = + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands(); + for (HystrixInvokableInfo command : executedCommands) { + if (command.getCommandKey().name().equals(key)) { + hystrixCommand = command; + break; + } + } + return hystrixCommand; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/cache/BasicCacheTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/cache/BasicCacheTest.java new file mode 100644 index 0000000..620f6ec --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/cache/BasicCacheTest.java @@ -0,0 +1,296 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCachingException; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.Profile; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.Before; +import org.junit.Test; + +import javax.annotation.PostConstruct; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getLastExecutedCommand; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Created by dmgcodevil + */ +public abstract class BasicCacheTest extends BasicHystrixTest { + + private UserService userService; + + @Before + public void setUp() throws Exception { + userService = createUserService(); + } + + protected abstract UserService createUserService(); + + /** + * Get-Set-Get with Request Cache Invalidation Test. + *

+ * given: + * command to get user by id, see {@link UserService#getUserById(String)} + * command to update user, see {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.common.domain.User)} + *

+ * when: + * 1. call {@link UserService#getUserById(String)} + * 2. call {@link UserService#getUserById(String)} + * 3. call {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.common.domain.User)} + * 4. call {@link UserService#getUserById(String)} + *

+ * then: + * at the first time "getUserById" command shouldn't retrieve value from cache + * at the second time "getUserById" command should retrieve value from cache + * "update" method should update an user and flush cache related to "getUserById" command + * after "update" method execution "getUserById" command shouldn't retrieve value from cache + */ + @Test + public void testGetSetGetUserCache_givenTwoCommands() { + + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); // initial name value + + user = userService.getUserById("1"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("name", user.getName()); // same name + + // create new user with same id but with new name + user = new User("1", "new_name"); + userService.update(user); // update the user + + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "update" + // method was invoked and a cache for "getUserById" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("new_name", user.getName()); + + // start a new request context + resetContext(); + + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + + } + + @Test + public void testGetSetGetUserCache_givenGetUserByEmailAndUpdateProfile() { + User user = userService.getUserByEmail("email"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); + assertEquals("email", user.getProfile().getEmail()); // initial email value + + user = userService.getUserByEmail("email"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("email", user.getProfile().getEmail()); // same email + + // create new user with same id but with new email + Profile profile = new Profile(); + profile.setEmail("new_email"); + user.setProfile(profile); + userService.updateProfile(user); // update the user profile + + user = userService.getUserByEmail("new_email"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "updateProfile" + // method was invoked and a cache for "getUserByEmail" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); + assertEquals("new_email", user.getProfile().getEmail()); + + // start a new request context + resetContext(); + + user = userService.getUserByEmail("new_email"); + getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } + + @Test + public void testGetSetGetUserCache_givenOneCommandAndOneMethodAnnotatedWithCacheRemove() { + // given + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); // initial name value + + user = userService.getUserById("1"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("name", user.getName()); // same name + + // when + userService.updateName("1", "new_name"); // update the user name + + // then + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "update" + // method was invoked and a cache for "getUserById" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("new_name", user.getName()); + + // start a new request context + resetContext(); + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } + + + @Test(expected = HystrixCachingException.class) + public void testGetUser_givenWrongCacheKeyMethodReturnType_shouldThrowException() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUserByName("name"); + } finally { + context.shutdown(); + } + } + + @Test(expected = HystrixCachingException.class) + public void testGetUserByName_givenNonexistentCacheKeyMethod_shouldThrowException() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUser(); + } finally { + context.shutdown(); + } + } + + public static class UserService { + private Map storage = new ConcurrentHashMap(); + + @PostConstruct + public void init() { + User user = new User("1", "name"); + Profile profile = new Profile(); + profile.setEmail("email"); + user.setProfile(profile); + storage.put("1", user); + } + + @CacheResult + @HystrixCommand + public User getUserById(@CacheKey String id) { + return storage.get(id); + } + + @CacheResult(cacheKeyMethod = "getUserByNameCacheKey") + @HystrixCommand + public User getUserByName(String name) { + return null; + } + + private Long getUserByNameCacheKey() { + return 0L; + } + + @CacheResult(cacheKeyMethod = "nonexistent") + @HystrixCommand + public User getUser() { + return null; + } + + @CacheResult(cacheKeyMethod = "getUserByEmailCacheKey") + @HystrixCommand + public User getUserByEmail(final String email) { + return Iterables.tryFind(storage.values(), new Predicate() { + @Override + public boolean apply(User input) { + return input.getProfile().getEmail().equalsIgnoreCase(email); + } + }).orNull(); + } + + private String getUserByEmailCacheKey(String email) { + return email; + } + + @CacheRemove(commandKey = "getUserById") + @HystrixCommand + public void update(@CacheKey("id") User user) { + storage.put(user.getId(), user); + } + + @CacheRemove(commandKey = "getUserByEmail") + @HystrixCommand + public void updateProfile(@CacheKey("profile.email") User user) { + storage.get(user.getId()).setProfile(user.getProfile()); + } + + @CacheRemove(commandKey = "getUserById") + public void updateName(@CacheKey String id, String name) { + storage.get(id).setName(name); + } + + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/collapser/BasicCollapserTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/collapser/BasicCollapserTest.java new file mode 100644 index 0000000..9e70e1d --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/collapser/BasicCollapserTest.java @@ -0,0 +1,328 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.collapser; + +import com.google.common.collect.Sets; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Created by dmgcodevil + */ +public abstract class BasicCollapserTest extends BasicHystrixTest { + + protected abstract UserService createUserService(); + + private UserService userService; + + @Before + public void setUp() throws Exception { + userService = createUserService(); + + } + + @Test + public void testGetUserById() throws ExecutionException, InterruptedException { + + Future f1 = userService.getUserById("1"); + Future f2 = userService.getUserById("2"); + Future f3 = userService.getUserById("3"); + Future f4 = userService.getUserById("4"); + Future f5 = userService.getUserById("5"); + + assertEquals("name: 1", f1.get().getName()); + assertEquals("name: 2", f2.get().getName()); + assertEquals("name: 3", f3.get().getName()); + assertEquals("name: 4", f4.get().getName()); + assertEquals("name: 5", f5.get().getName()); + // assert that the batch command 'getUserByIds' was in fact + // executed and that it executed only once + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + // assert the command is the one we're expecting + assertEquals("getUserByIds", command.getCommandKey().name()); + // confirm that it was a COLLAPSED command execution + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + // and that it was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testReactive() throws Exception { + + final Observable u1 = userService.getUserByIdReactive("1"); + final Observable u2 = userService.getUserByIdReactive("2"); + final Observable u3 = userService.getUserByIdReactive("3"); + final Observable u4 = userService.getUserByIdReactive("4"); + final Observable u5 = userService.getUserByIdReactive("5"); + + final Iterable users = Observable.merge(u1, u2, u3, u4, u5).toBlocking().toIterable(); + + Set expectedIds = Sets.newHashSet("1", "2", "3", "4", "5"); + for (User cUser : users) { + assertEquals(expectedIds.remove(cUser.getId()), true); + } + assertEquals(expectedIds.isEmpty(), true); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + // assert the command is the one we're expecting + assertEquals("getUserByIds", command.getCommandKey().name()); + // confirm that it was a COLLAPSED command execution + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + // and that it was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testGetUserByIdWithFallback() throws ExecutionException, InterruptedException { + Future f1 = userService.getUserByIdWithFallback("1"); + Future f2 = userService.getUserByIdWithFallback("2"); + Future f3 = userService.getUserByIdWithFallback("3"); + Future f4 = userService.getUserByIdWithFallback("4"); + Future f5 = userService.getUserByIdWithFallback("5"); + + assertEquals("name: 1", f1.get().getName()); + assertEquals("name: 2", f2.get().getName()); + assertEquals("name: 3", f3.get().getName()); + assertEquals("name: 4", f4.get().getName()); + assertEquals("name: 5", f5.get().getName()); + // two command should be executed: "getUserByIdWithFallback" and "getUserByIdsWithFallback" + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo getUserByIdsWithFallback = getHystrixCommandByKey("getUserByIdsWithFallback"); + com.netflix.hystrix.HystrixInvokableInfo getUserByIdsFallback = getHystrixCommandByKey("getUserByIdsFallback"); + // confirm that command has failed + assertTrue(getUserByIdsWithFallback.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserByIdsWithFallback.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + // and that fallback was successful + assertTrue(getUserByIdsFallback.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testGetUserByIdWithFallbackWithThrowableParam() throws ExecutionException, InterruptedException { + Future f1 = userService.getUserByIdWithFallbackWithThrowableParam("1"); + Future f2 = userService.getUserByIdWithFallbackWithThrowableParam("2"); + Future f3 = userService.getUserByIdWithFallbackWithThrowableParam("3"); + Future f4 = userService.getUserByIdWithFallbackWithThrowableParam("4"); + Future f5 = userService.getUserByIdWithFallbackWithThrowableParam("5"); + + assertEquals("name: 1", f1.get().getName()); + assertEquals("name: 2", f2.get().getName()); + assertEquals("name: 3", f3.get().getName()); + assertEquals("name: 4", f4.get().getName()); + assertEquals("name: 5", f5.get().getName()); + // 4 commands should be executed + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo batchCommand = getHystrixCommandByKey("getUserByIdsThrowsException"); + com.netflix.hystrix.HystrixInvokableInfo fallback1 = getHystrixCommandByKey("getUserByIdsFallbackWithThrowableParam1"); + com.netflix.hystrix.HystrixInvokableInfo fallback2 = getHystrixCommandByKey("getUserByIdsFallbackWithThrowableParam2"); + com.netflix.hystrix.HystrixInvokableInfo fallback3 = getHystrixCommandByKey("getUserByIdsFallbackWithThrowableParam3"); + // confirm that command has failed + assertTrue(batchCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + + assertTrue(fallback1.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(fallback2.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(fallback2.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + + // and that last fallback3 was successful + assertTrue(fallback3.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test(expected = IllegalStateException.class) + public void testGetUserByIdWrongBatchMethodArgType() { + userService.getUserByIdWrongBatchMethodArgType("1"); + } + + @Test(expected = IllegalStateException.class) + public void testGetUserByIdWrongBatchMethodReturnType() { + userService.getUserByIdWrongBatchMethodArgType("1"); + } + + @Test(expected = IllegalStateException.class) + public void testGetUserByIdWrongCollapserMethodReturnType() { + userService.getUserByIdWrongCollapserMethodReturnType("1"); + } + + @Test(expected = IllegalStateException.class) + public void testGetUserByIdWrongCollapserMultipleArgs() { + userService.getUserByIdWrongCollapserMultipleArgs("1", "2"); + } + + @Test(expected = IllegalStateException.class) + public void testGetUserByIdWrongCollapserNoArgs() { + userService.getUserByIdWrongCollapserNoArgs(); + } + + public static class UserService { + + public static final Logger log = LoggerFactory.getLogger(UserService.class); + public static final User DEFAULT_USER = new User("def", "def"); + + + @HystrixCollapser(batchMethod = "getUserByIds", + collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) + public Future getUserById(String id) { + return null; + } + + @HystrixCollapser(batchMethod = "getUserByIdsWithFallback", + collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) + public Future getUserByIdWithFallback(String id) { + return null; + } + + @HystrixCollapser(batchMethod = "getUserByIds", + collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) + public Observable getUserByIdReactive(String id) { + return null; + } + + @HystrixCollapser(batchMethod = "getUserByIdsThrowsException", + collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) + public Future getUserByIdWithFallbackWithThrowableParam(String id) { + return null; + } + + @HystrixCommand( + fallbackMethod = "getUserByIdsFallbackWithThrowableParam1", + commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "10000")// for debug + }) + public List getUserByIdsThrowsException(List ids) { + throw new RuntimeException("getUserByIdsFails failed"); + } + + @HystrixCommand(fallbackMethod = "getUserByIdsFallbackWithThrowableParam2") + private List getUserByIdsFallbackWithThrowableParam1(List ids, Throwable e) { + if (e.getMessage().equals("getUserByIdsFails failed")) { + throw new RuntimeException("getUserByIdsFallbackWithThrowableParam1 failed"); + } + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + return users; + } + + @HystrixCommand(fallbackMethod = "getUserByIdsFallbackWithThrowableParam3") + private List getUserByIdsFallbackWithThrowableParam2(List ids) { + throw new RuntimeException("getUserByIdsFallbackWithThrowableParam2 failed"); + } + + @HystrixCommand + private List getUserByIdsFallbackWithThrowableParam3(List ids, Throwable e) { + if (!e.getMessage().equals("getUserByIdsFallbackWithThrowableParam2 failed")) { + throw new RuntimeException("getUserByIdsFallbackWithThrowableParam3 failed"); + } + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + return users; + } + + @HystrixCommand(commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "10000")// for debug + }) + public List getUserByIds(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + log.debug("executing on thread id: {}", Thread.currentThread().getId()); + return users; + } + + @HystrixCommand(fallbackMethod = "getUserByIdsFallback", + commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "10000")// for debug + }) + public List getUserByIdsWithFallback(List ids) { + throw new RuntimeException("not found"); + } + + + @HystrixCommand + private List getUserByIdsFallback(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + return users; + } + + // wrong return type, expected: Future or User, because batch command getUserByIds returns List + @HystrixCollapser(batchMethod = "getUserByIds") + public Long getUserByIdWrongCollapserMethodReturnType(String id) { + return null; + } + + @HystrixCollapser(batchMethod = "getUserByIds") + public Future getUserByIdWrongCollapserMultipleArgs(String id, String name) { + return null; + } + + @HystrixCollapser(batchMethod = "getUserByIds") + public Future getUserByIdWrongCollapserNoArgs() { + return null; + } + + @HystrixCollapser(batchMethod = "getUserByIdsWrongBatchMethodArgType") + public Future getUserByIdWrongBatchMethodArgType(String id) { + return null; + } + + // wrong arg type, expected: List + @HystrixCommand + public List getUserByIdsWrongBatchMethodArgType(List ids) { + return null; + } + + @HystrixCollapser(batchMethod = "getUserByIdsWrongBatchMethodReturnType") + public Future getUserByIdWrongBatchMethodReturnType(String id) { + return null; + } + + // wrong return type, expected: List + @HystrixCommand + public List getUserByIdsWrongBatchMethodReturnType(List ids) { + return null; + } + + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/command/BasicCommandTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/command/BasicCommandTest.java new file mode 100644 index 0000000..1fbb140 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/command/BasicCommandTest.java @@ -0,0 +1,192 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.command; + + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.command.AsyncResult; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public abstract class BasicCommandTest extends BasicHystrixTest { + + private UserService userService; + private AdvancedUserService advancedUserService; + private GenericService genericUserService; + + @Before + public void setUp() throws Exception { + userService = createUserService(); + advancedUserService = createAdvancedUserServiceService(); + genericUserService = createGenericUserService(); + } + + @Test + public void testGetUserAsync() throws ExecutionException, InterruptedException { + Future f1 = userService.getUserAsync("1", "name: "); + + assertEquals("name: 1", f1.get().getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo command = getCommand(); + // assert the command key name is the we're expecting + assertEquals("GetUserCommand", command.getCommandKey().name()); + // assert the command group key name is the we're expecting + assertEquals("UserService", command.getCommandGroup().name()); + // assert the command thread pool key name is the we're expecting + assertEquals("CommandTestAsync", command.getThreadPoolKey().name()); + // it was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testGetUserSync() { + User u1 = userService.getUserSync("1", "name: "); + assertGetUserSnycCommandExecuted(u1); + } + + @Test + public void shouldWorkWithInheritedMethod() { + User u1 = advancedUserService.getUserSync("1", "name: "); + assertGetUserSnycCommandExecuted(u1); + } + + @Test + public void should_work_with_parameterized_method() throws Exception { + assertEquals(Integer.valueOf(1), userService.echo(1)); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + assertTrue(getCommand().getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void should_work_with_parameterized_asyncMethod() throws Exception { + assertEquals(Integer.valueOf(1), userService.echoAsync(1).get()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + assertTrue(getCommand().getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void should_work_with_genericClass_fallback() { + User user = genericUserService.getByKeyForceFail("1", 2L); + assertEquals("name: 2", user.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + + assertEquals("getByKeyForceFail", command.getCommandKey().name()); + // confirm that command has failed + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + + private void assertGetUserSnycCommandExecuted(User u1) { + assertEquals("name: 1", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo command = getCommand(); + assertEquals("getUserSync", command.getCommandKey().name()); + assertEquals("UserGroup", command.getCommandGroup().name()); + assertEquals("UserGroup", command.getThreadPoolKey().name()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + private com.netflix.hystrix.HystrixInvokableInfo getCommand() { + return HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + } + + protected abstract UserService createUserService(); + protected abstract AdvancedUserService createAdvancedUserServiceService(); + protected abstract GenericService createGenericUserService(); + + public interface GenericService { + V getByKey(K1 key1, K2 key2); + V getByKeyForceFail(K1 key, K2 key2); + V fallback(K1 key, K2 key2); + } + + public static class GenericUserService implements GenericService { + + @HystrixCommand(fallbackMethod = "fallback") + @Override + public User getByKey(String sKey, Long lKey) { + return new User(sKey, "name: " + lKey); // it should be network call + } + + @HystrixCommand(fallbackMethod = "fallback") + @Override + public User getByKeyForceFail(String sKey, Long lKey) { + throw new RuntimeException("force fail"); + } + + @Override + public User fallback(String sKey, Long lKey) { + return new User(sKey, "name: " + lKey); + } + + } + + public static class UserService { + + @HystrixCommand(commandKey = "GetUserCommand", threadPoolKey = "CommandTestAsync") + public Future getUserAsync(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + return new User(id, name + id); // it should be network call + } + }; + } + + @HystrixCommand(groupKey = "UserGroup") + public User getUserSync(String id, String name) { + return new User(id, name + id); // it should be network call + } + + @HystrixCommand + public T echo(T value) { + return value; + } + + @HystrixCommand + public Future echoAsync(final T value) { + return new AsyncResult() { + @Override + public T invoke() { + return value; + } + }; + } + + } + + public static class AdvancedUserService extends UserService { + + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/collapser/BasicCollapserPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/collapser/BasicCollapserPropertiesTest.java new file mode 100644 index 0000000..cbf9b3c --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/collapser/BasicCollapserPropertiesTest.java @@ -0,0 +1,96 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.configuration.collapser; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Created by dmgcodevil + */ +public abstract class BasicCollapserPropertiesTest extends BasicHystrixTest { + + private UserService userService; + + protected abstract UserService createUserService(); + + @Before + public void setUp() throws Exception { + userService = createUserService(); + } + + @Test + public void testCollapser() throws ExecutionException, InterruptedException { + + User u1 = userService.getUser("1"); + User u2 = userService.getUser("2"); + User u3 = userService.getUser("3"); + User u4 = userService.getUser("4"); + + assertEquals("name: 1", u1.getName()); + assertEquals("name: 2", u2.getName()); + assertEquals("name: 3", u3.getName()); + assertEquals("name: 4", u4.getName()); + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("getUsers", command.getCommandKey().name()); + // confirm that it was a COLLAPSED command execution + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + // and that it was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + public static class UserService { + + @HystrixCollapser( + batchMethod = "getUsers", + collapserKey = "GetUserCollapser", collapserProperties = { + @HystrixProperty(name = TIMER_DELAY_IN_MILLISECONDS, value = "200"), + @HystrixProperty(name = MAX_REQUESTS_IN_BATCH, value = "1"), + }) + public User getUser(String id) { + return null; + } + + @HystrixCommand + public List getUsers(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + return users; + } + + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java new file mode 100644 index 0000000..cdf815e --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java @@ -0,0 +1,139 @@ +package com.netflix.hystrix.contrib.javanica.test.common.configuration.command; + +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import org.junit.Before; +import org.junit.Test; + +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS; +import static org.junit.Assert.assertEquals; + +/** + * Created by dmgcodevil. + */ +public abstract class BasicCommandDefaultPropertiesTest extends BasicHystrixTest { + + private Service service; + + protected abstract Service createService(); + + @Before + public void setUp() throws Exception { + service = createService(); + } + + @Test + public void testCommandInheritsDefaultGroupKey() { + service.commandInheritsDefaultProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("DefaultGroupKey", command.getCommandGroup().name()); + } + + @Test + public void testCommandOverridesDefaultGroupKey() { + service.commandOverridesGroupKey(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("SpecificGroupKey", command.getCommandGroup().name()); + } + + @Test + public void testCommandInheritsDefaultThreadPoolKey() { + service.commandInheritsDefaultProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("DefaultThreadPoolKey", command.getThreadPoolKey().name()); + } + + @Test + public void testCommandOverridesDefaultThreadPoolKey() { + service.commandOverridesThreadPoolKey(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("SpecificThreadPoolKey", command.getThreadPoolKey().name()); + } + + @Test + public void testCommandInheritsDefaultCommandProperties() { + service.commandInheritsDefaultProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals(456, command.getProperties().executionTimeoutInMilliseconds().get().intValue()); + } + + @Test + public void testCommandOverridesDefaultCommandProperties() { + service.commandOverridesDefaultCommandProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals(654, command.getProperties().executionTimeoutInMilliseconds().get().intValue()); + } + + @Test + public void testCommandInheritsThreadPollProperties() { + service.commandInheritsDefaultProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(command); + + assertEquals(123, properties.maxQueueSize().get().intValue()); + } + + @Test + public void testCommandOverridesDefaultThreadPollProperties() { + service.commandOverridesDefaultThreadPoolProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(command); + + assertEquals(321, properties.maxQueueSize().get().intValue()); + } + + @DefaultProperties(groupKey = "DefaultGroupKey", threadPoolKey = "DefaultThreadPoolKey", + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "456") + }, + threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "123") + } + ) + public static class Service { + + @HystrixCommand + public Object commandInheritsDefaultProperties() { + return null; + } + + @HystrixCommand(groupKey = "SpecificGroupKey") + public Object commandOverridesGroupKey() { + return null; + } + + @HystrixCommand(threadPoolKey = "SpecificThreadPoolKey") + public Object commandOverridesThreadPoolKey() { + return null; + } + + @HystrixCommand(commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "654") + }) + public Object commandOverridesDefaultCommandProperties() { + return null; + } + + @HystrixCommand(threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "321") + }) + public Object commandOverridesDefaultThreadPoolProperties() { + return null; + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandPropertiesTest.java new file mode 100644 index 0000000..f3f4aff --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandPropertiesTest.java @@ -0,0 +1,217 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.configuration.command; + +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import org.junit.Before; +import org.junit.Test; + +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_FORCE_CLOSED; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_FORCE_OPEN; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_TIMEOUT_ENABLED; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.FALLBACK_ENABLED; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.METRICS_ROLLING_PERCENTILE_BUCKET_SIZE; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.METRICS_ROLLING_PERCENTILE_ENABLED; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.METRICS_ROLLING_PERCENTILE_NUM_BUCKETS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.METRICS_ROLLING_STATS_NUM_BUCKETS; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.REQUEST_CACHE_ENABLED; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.REQUEST_LOG_ENABLED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Created by dmgcodevil + */ +public abstract class BasicCommandPropertiesTest extends BasicHystrixTest { + + private UserService userService; + + protected abstract UserService createUserService(); + + @Before + public void setUp() throws Exception { + userService = createUserService(); + } + + @Test + public void testGetUser() throws NoSuchFieldException, IllegalAccessException { + User u1 = userService.getUser("1", "name: "); + assertEquals("name: 1", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("GetUserCommand", command.getCommandKey().name()); + assertEquals("UserGroupKey", command.getCommandGroup().name()); + assertEquals("Test", command.getThreadPoolKey().name()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + // assert properties + assertEquals(110, command.getProperties().executionTimeoutInMilliseconds().get().intValue()); + assertEquals(false, command.getProperties().executionIsolationThreadInterruptOnTimeout().get()); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(command); + + assertEquals(30, (int) properties.coreSize().get()); + assertEquals(35, (int) properties.maximumSize().get()); + assertEquals(true, properties.getAllowMaximumSizeToDivergeFromCoreSize().get()); + assertEquals(101, (int) properties.maxQueueSize().get()); + assertEquals(2, (int) properties.keepAliveTimeMinutes().get()); + assertEquals(15, (int) properties.queueSizeRejectionThreshold().get()); + assertEquals(1440, (int) properties.metricsRollingStatisticalWindowInMilliseconds().get()); + assertEquals(12, (int) properties.metricsRollingStatisticalWindowBuckets().get()); + } + + @Test + public void testGetUserDefaultPropertiesValues() { + User u1 = userService.getUserDefProperties("1", "name: "); + assertEquals("name: 1", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("getUserDefProperties", command.getCommandKey().name()); + assertEquals("UserService", command.getCommandGroup().name()); + assertEquals("UserService", command.getThreadPoolKey().name()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testGetUserDefGroupKeyWithSpecificThreadPoolKey() { + User u1 = userService.getUserDefGroupKeyWithSpecificThreadPoolKey("1", "name: "); + assertEquals("name: 1", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("getUserDefGroupKeyWithSpecificThreadPoolKey", command.getCommandKey().name()); + assertEquals("UserService", command.getCommandGroup().name()); + assertEquals("CustomThreadPool", command.getThreadPoolKey().name()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testHystrixCommandProperties() { + User u1 = userService.getUsingAllCommandProperties("1", "name: "); + assertEquals("name: 1", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + // assert properties + assertEquals(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE, command.getProperties().executionIsolationStrategy().get()); + assertEquals(500, command.getProperties().executionTimeoutInMilliseconds().get().intValue()); + assertEquals(true, command.getProperties().executionTimeoutEnabled().get().booleanValue()); + assertEquals(false, command.getProperties().executionIsolationThreadInterruptOnTimeout().get().booleanValue()); + assertEquals(10, command.getProperties().executionIsolationSemaphoreMaxConcurrentRequests().get().intValue()); + assertEquals(15, command.getProperties().fallbackIsolationSemaphoreMaxConcurrentRequests().get().intValue()); + assertEquals(false, command.getProperties().fallbackEnabled().get().booleanValue()); + assertEquals(false, command.getProperties().circuitBreakerEnabled().get().booleanValue()); + assertEquals(30, command.getProperties().circuitBreakerRequestVolumeThreshold().get().intValue()); + assertEquals(250, command.getProperties().circuitBreakerSleepWindowInMilliseconds().get().intValue()); + assertEquals(60, command.getProperties().circuitBreakerErrorThresholdPercentage().get().intValue()); + assertEquals(false, command.getProperties().circuitBreakerForceOpen().get().booleanValue()); + assertEquals(true, command.getProperties().circuitBreakerForceClosed().get().booleanValue()); + assertEquals(false, command.getProperties().metricsRollingPercentileEnabled().get().booleanValue()); + assertEquals(400, command.getProperties().metricsRollingPercentileWindowInMilliseconds().get().intValue()); + assertEquals(5, command.getProperties().metricsRollingPercentileWindowBuckets().get().intValue()); + assertEquals(6, command.getProperties().metricsRollingPercentileBucketSize().get().intValue()); + assertEquals(10, command.getProperties().metricsRollingStatisticalWindowBuckets().get().intValue()); + assertEquals(500, command.getProperties().metricsRollingStatisticalWindowInMilliseconds().get().intValue()); + assertEquals(312, command.getProperties().metricsHealthSnapshotIntervalInMilliseconds().get().intValue()); + assertEquals(false, command.getProperties().requestCacheEnabled().get().booleanValue()); + assertEquals(true, command.getProperties().requestLogEnabled().get().booleanValue()); + } + + public static class UserService { + + @HystrixCommand(commandKey = "GetUserCommand", groupKey = "UserGroupKey", threadPoolKey = "Test", + commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "110"), + @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "false") + }, + threadPoolProperties = { + @HystrixProperty(name = "coreSize", value = "30"), + @HystrixProperty(name = "maximumSize", value = "35"), + @HystrixProperty(name = "allowMaximumSizeToDivergeFromCoreSize", value = "true"), + @HystrixProperty(name = "maxQueueSize", value = "101"), + @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), + @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"), + @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"), + @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440") + }) + public User getUser(String id, String name) { + return new User(id, name + id); // it should be network call + } + + @HystrixCommand + public User getUserDefProperties(String id, String name) { + return new User(id, name + id); // it should be network call + } + + @HystrixCommand(threadPoolKey = "CustomThreadPool") + public User getUserDefGroupKeyWithSpecificThreadPoolKey(String id, String name) { + return new User(id, name + id); // it should be network call + } + + @HystrixCommand( + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"), + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "500"), + @HystrixProperty(name = EXECUTION_TIMEOUT_ENABLED, value = "true"), + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT, value = "false"), + @HystrixProperty(name = EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "10"), + @HystrixProperty(name = FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "15"), + @HystrixProperty(name = FALLBACK_ENABLED, value = "false"), + @HystrixProperty(name = CIRCUIT_BREAKER_ENABLED, value = "false"), + @HystrixProperty(name = CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "30"), + @HystrixProperty(name = CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "250"), + @HystrixProperty(name = CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "60"), + @HystrixProperty(name = CIRCUIT_BREAKER_FORCE_OPEN, value = "false"), + @HystrixProperty(name = CIRCUIT_BREAKER_FORCE_CLOSED, value = "true"), + @HystrixProperty(name = METRICS_ROLLING_PERCENTILE_ENABLED, value = "false"), + @HystrixProperty(name = METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS, value = "400"), + @HystrixProperty(name = METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, value = "500"), + @HystrixProperty(name = METRICS_ROLLING_STATS_NUM_BUCKETS, value = "10"), + @HystrixProperty(name = METRICS_ROLLING_PERCENTILE_NUM_BUCKETS, value = "5"), + @HystrixProperty(name = METRICS_ROLLING_PERCENTILE_BUCKET_SIZE, value = "6"), + @HystrixProperty(name = METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS, value = "312"), + @HystrixProperty(name = REQUEST_CACHE_ENABLED, value = "false"), + @HystrixProperty(name = REQUEST_LOG_ENABLED, value = "true") + } + ) + public User getUsingAllCommandProperties(String id, String name) { + return new User(id, name + id); // it should be network call + } + + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/fallback/BasicFallbackDefaultPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/fallback/BasicFallbackDefaultPropertiesTest.java new file mode 100644 index 0000000..9f96a7e --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/fallback/BasicFallbackDefaultPropertiesTest.java @@ -0,0 +1,149 @@ +package com.netflix.hystrix.contrib.javanica.test.common.configuration.fallback; + +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import org.junit.Before; +import org.junit.Test; + +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS; +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; + + +public abstract class BasicFallbackDefaultPropertiesTest extends BasicHystrixTest { + + private Service service; + + protected abstract Service createService(); + + @Before + public void setUp() throws Exception { + service = createService(); + } + + @Test + public void testFallbackInheritsDefaultGroupKey() { + service.commandWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + assertEquals("DefaultGroupKey", fallbackCommand.getCommandGroup().name()); + } + + @Test + public void testFallbackInheritsDefaultThreadPoolKey() { + service.commandWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + assertEquals("DefaultThreadPoolKey", fallbackCommand.getThreadPoolKey().name()); + } + + @Test + public void testFallbackInheritsDefaultCommandProperties() { + service.commandWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + assertEquals(456, fallbackCommand.getProperties().executionTimeoutInMilliseconds().get().intValue()); + } + + @Test + public void testFallbackInheritsThreadPollProperties() { + service.commandWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(fallbackCommand); + + assertEquals(123, properties.maxQueueSize().get().intValue()); + } + + @Test + public void testFallbackOverridesDefaultGroupKey() { + service.commandWithFallbackOverridesDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackOverridesDefaultProperties"); + assertEquals("FallbackGroupKey", fallbackCommand.getCommandGroup().name()); + } + + @Test + public void testFallbackOverridesDefaultThreadPoolKey() { + service.commandWithFallbackOverridesDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackOverridesDefaultProperties"); + assertEquals("FallbackThreadPoolKey", fallbackCommand.getThreadPoolKey().name()); + } + + @Test + public void testFallbackOverridesDefaultCommandProperties() { + service.commandWithFallbackOverridesDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackOverridesDefaultProperties"); + assertEquals(654, fallbackCommand.getProperties().executionTimeoutInMilliseconds().get().intValue()); + } + + @Test + public void testFallbackOverridesThreadPollProperties() { + service.commandWithFallbackOverridesDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackOverridesDefaultProperties"); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(fallbackCommand); + + assertEquals(321, properties.maxQueueSize().get().intValue()); + } + + @Test + public void testCommandOverridesDefaultPropertiesWithFallbackInheritsDefaultProperties(){ + service.commandOverridesDefaultPropertiesWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + HystrixThreadPoolProperties properties = getThreadPoolProperties(fallbackCommand); + assertEquals("DefaultGroupKey", fallbackCommand.getCommandGroup().name()); + assertEquals("DefaultThreadPoolKey", fallbackCommand.getThreadPoolKey().name()); + assertEquals(456, fallbackCommand.getProperties().executionTimeoutInMilliseconds().get().intValue()); + assertEquals(123, properties.maxQueueSize().get().intValue()); + } + + @DefaultProperties(groupKey = "DefaultGroupKey", + threadPoolKey = "DefaultThreadPoolKey", + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "456") + }, + threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "123") + }) + public static class Service { + + @HystrixCommand(fallbackMethod = "fallbackInheritsDefaultProperties") + public Object commandWithFallbackInheritsDefaultProperties() { + throw new RuntimeException(); + } + + @HystrixCommand(fallbackMethod = "fallbackOverridesDefaultProperties") + public Object commandWithFallbackOverridesDefaultProperties() { + throw new RuntimeException(); + } + + @HystrixCommand(groupKey = "CommandGroupKey", + threadPoolKey = "CommandThreadPoolKey", + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "654") + }, + threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "321") + }, fallbackMethod = "fallbackInheritsDefaultProperties") + public Object commandOverridesDefaultPropertiesWithFallbackInheritsDefaultProperties() { + throw new RuntimeException(); + } + + @HystrixCommand + private Object fallbackInheritsDefaultProperties() { + return null; + } + + @HystrixCommand(groupKey = "FallbackGroupKey", + threadPoolKey = "FallbackThreadPoolKey", + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "654") + }, + threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "321") + }) + private Object fallbackOverridesDefaultProperties() { + return null; + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/Domain.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/Domain.java new file mode 100644 index 0000000..a56e4d8 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/Domain.java @@ -0,0 +1,20 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.domain; + + +public class Domain { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/Profile.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/Profile.java new file mode 100644 index 0000000..7afc3f0 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/Profile.java @@ -0,0 +1,31 @@ +/** + * 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.contrib.javanica.test.common.domain; + +/** + * Created by dmgcodevil on 1/9/2015. + */ +public class Profile { + private String email; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/User.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/User.java new file mode 100644 index 0000000..03df543 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/domain/User.java @@ -0,0 +1,96 @@ +/** + * 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.contrib.javanica.test.common.domain; + +/** + * Simple domain object for tests. + */ +public class User extends Domain{ + + private String id; + private String name; + private Profile profile; + + + public User() { + } + + public User(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Profile getProfile() { + return profile; + } + + public void setProfile(Profile profile) { + this.profile = profile; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("User{"); + sb.append("id='").append(id).append('\''); + sb.append(", name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if(this == o) { + return true; + } + if(!(o instanceof User)) { + return false; + } + + User user = (User) o; + + if(id != null ? !id.equals(user.id) : user.id != null) { + return false; + } + if(name != null ? !name.equals(user.name) : user.name != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java new file mode 100644 index 0000000..7b1c0e1 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java @@ -0,0 +1,123 @@ +package com.netflix.hystrix.contrib.javanica.test.common.error; + +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Test for {@link DefaultProperties#ignoreExceptions()} feature. + * + *

+ * Created by dmgcodevil. + */ +public abstract class BasicDefaultIgnoreExceptionsTest { + private Service service; + + @Before + public void setUp() throws Exception { + service = createService(); + } + + protected abstract Service createService(); + + @Test(expected = BadRequestException.class) + public void testDefaultIgnoreException() { + service.commandInheritsDefaultIgnoreExceptions(); + } + + @Test(expected = SpecificException.class) + public void testCommandOverridesDefaultIgnoreExceptions() { + service.commandOverridesDefaultIgnoreExceptions(SpecificException.class); + } + + @Test(expected = BadRequestException.class) + public void testCommandOverridesDefaultIgnoreExceptions_nonIgnoreExceptionShouldBePropagated() { + // method throws BadRequestException that isn't ignored + service.commandOverridesDefaultIgnoreExceptions(BadRequestException.class); + } + + @Ignore // https://github.com/Netflix/Hystrix/issues/993#issuecomment-229542203 + @Test(expected = BadRequestException.class) + public void testFallbackCommandInheritsDefaultIgnoreException() { + service.commandWithFallbackInheritsDefaultIgnoreExceptions(); + } + + @Ignore // https://github.com/Netflix/Hystrix/issues/993#issuecomment-229542203 + @Test(expected = SpecificException.class) + public void testFallbackCommandOverridesDefaultIgnoreExceptions() { + service.commandWithFallbackOverridesDefaultIgnoreExceptions(SpecificException.class); + } + + @Test(expected = BadRequestException.class) + public void testFallbackCommandOverridesDefaultIgnoreExceptions_nonIgnoreExceptionShouldBePropagated() { + service.commandWithFallbackOverridesDefaultIgnoreExceptions(BadRequestException.class); + } + + @DefaultProperties(ignoreExceptions = BadRequestException.class) + public static class Service { + @HystrixCommand + public Object commandInheritsDefaultIgnoreExceptions() throws BadRequestException { + // this exception will be ignored (wrapped in HystrixBadRequestException) because specified in default ignore exceptions + throw new BadRequestException("from 'commandInheritsIgnoreExceptionsFromDefault'"); + } + + @HystrixCommand(ignoreExceptions = SpecificException.class) + public Object commandOverridesDefaultIgnoreExceptions(Class errorType) throws BadRequestException, SpecificException { + if(errorType.equals(BadRequestException.class)){ + // isn't ignored because command doesn't specify this exception type in 'ignoreExceptions' + throw new BadRequestException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + // something went wrong, this error is ignored because specified in the command's ignoreExceptions + throw new SpecificException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + + @HystrixCommand(fallbackMethod = "fallbackInheritsDefaultIgnoreExceptions") + public Object commandWithFallbackInheritsDefaultIgnoreExceptions() throws SpecificException { + // isn't ignored, need to trigger fallback + throw new SpecificException("from 'commandWithFallbackInheritsDefaultIgnoreExceptions'"); + } + + @HystrixCommand + private Object fallbackInheritsDefaultIgnoreExceptions() throws BadRequestException { + // should be ignored because specified in global ignore exception, fallback command inherits default ignore exceptions + throw new BadRequestException("from 'fallbackInheritsDefaultIgnoreExceptions'"); + } + + @HystrixCommand(fallbackMethod = "fallbackOverridesDefaultIgnoreExceptions") + public Object commandWithFallbackOverridesDefaultIgnoreExceptions(Class errorType) { + // isn't ignored, need to trigger fallback + throw new SpecificException(); + } + + @HystrixCommand(ignoreExceptions = SpecificException.class) + private Object fallbackOverridesDefaultIgnoreExceptions(Class errorType) { + if(errorType.equals(BadRequestException.class)){ + // isn't ignored because fallback doesn't specify this exception type in 'ignoreExceptions' + throw new BadRequestException("from 'fallbackOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + // something went wrong, this error is ignored because specified in the fallback's ignoreExceptions + throw new SpecificException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + } + + public static final class BadRequestException extends RuntimeException { + public BadRequestException() { + } + + public BadRequestException(String message) { + super(message); + } + } + + public static final class SpecificException extends RuntimeException { + public SpecificException() { + } + + public SpecificException(String message) { + super(message); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultRaiseHystrixExceptionsTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultRaiseHystrixExceptionsTest.java new file mode 100644 index 0000000..95c862e --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultRaiseHystrixExceptionsTest.java @@ -0,0 +1,149 @@ +package com.netflix.hystrix.contrib.javanica.test.common.error; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import com.netflix.hystrix.Hystrix; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixException; +import com.netflix.hystrix.exception.HystrixRuntimeException; + +import rx.Observable; +import rx.observers.TestSubscriber; + +/** + * Created by Mike Cowan + */ +public abstract class BasicDefaultRaiseHystrixExceptionsTest { + + private Service service; + + @Before + public void setUp() throws Exception { + service = createService(); + } + + protected abstract Service createService(); + + @Test(expected = BadRequestException.class) + public void testDefaultIgnoreException() { + service.commandInheritsDefaultIgnoreExceptions(); + } + + @Test(expected = SpecificException.class) + public void testCommandOverridesDefaultIgnoreExceptions() { + service.commandOverridesDefaultIgnoreExceptions(SpecificException.class); + } + + @Test(expected = HystrixRuntimeException.class) + public void testCommandOverridesDefaultIgnoreExceptions_nonIgnoreExceptionShouldBePropagated() { + // method throws BadRequestException that isn't ignored + service.commandOverridesDefaultIgnoreExceptions(BadRequestException.class); + } + + @Ignore // https://github.com/Netflix/Hystrix/issues/993#issuecomment-229542203 + @Test(expected = BadRequestException.class) + public void testFallbackCommandInheritsDefaultIgnoreException() { + service.commandWithFallbackInheritsDefaultIgnoreExceptions(); + } + + @Ignore // https://github.com/Netflix/Hystrix/issues/993#issuecomment-229542203 + @Test(expected = SpecificException.class) + public void testFallbackCommandOverridesDefaultIgnoreExceptions() { + service.commandWithFallbackOverridesDefaultIgnoreExceptions(SpecificException.class); + } + + @Test(expected = HystrixRuntimeException.class) + public void testFallbackCommandOverridesDefaultIgnoreExceptions_nonIgnoreExceptionShouldBePropagated() { + service.commandWithFallbackOverridesDefaultIgnoreExceptions(BadRequestException.class); + } + + @Test(expected = HystrixRuntimeException.class) + public void testRaiseHystrixRuntimeException() { + service.commandShouldRaiseHystrixRuntimeException(); + } + + @Test + public void testObservableRaiseHystrixRuntimeException() { + TestSubscriber testSubscriber = new TestSubscriber(); + service.observableCommandShouldRaiseHystrixRuntimeException().subscribe(testSubscriber); + testSubscriber.assertError(HystrixRuntimeException.class); + } + + @DefaultProperties(ignoreExceptions = BadRequestException.class, raiseHystrixExceptions = {HystrixException.RUNTIME_EXCEPTION}) + public static class Service { + @HystrixCommand + public Object commandShouldRaiseHystrixRuntimeException() throws SpecificException { + throw new SpecificException("from 'commandShouldRaiseHystrixRuntimeException'"); + } + + @HystrixCommand + public Observable observableCommandShouldRaiseHystrixRuntimeException() throws SpecificException { + return Observable.error(new SpecificException("from 'observableCommandShouldRaiseHystrixRuntimeException'")); + } + + @HystrixCommand + public Object commandInheritsDefaultIgnoreExceptions() throws BadRequestException { + // this exception will be ignored (wrapped in HystrixBadRequestException) because specified in default ignore exceptions + throw new BadRequestException("from 'commandInheritsIgnoreExceptionsFromDefault'"); + } + + @HystrixCommand(ignoreExceptions = SpecificException.class) + public Object commandOverridesDefaultIgnoreExceptions(Class errorType) throws BadRequestException, SpecificException { + if(errorType.equals(BadRequestException.class)){ + // isn't ignored because command doesn't specify this exception type in 'ignoreExceptions' + throw new BadRequestException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + // something went wrong, this error is ignored because specified in the command's ignoreExceptions + throw new SpecificException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + + @HystrixCommand(fallbackMethod = "fallbackInheritsDefaultIgnoreExceptions") + public Object commandWithFallbackInheritsDefaultIgnoreExceptions() throws SpecificException { + // isn't ignored, need to trigger fallback + throw new SpecificException("from 'commandWithFallbackInheritsDefaultIgnoreExceptions'"); + } + + @HystrixCommand + private Object fallbackInheritsDefaultIgnoreExceptions() throws BadRequestException { + // should be ignored because specified in global ignore exception, fallback command inherits default ignore exceptions + throw new BadRequestException("from 'fallbackInheritsDefaultIgnoreExceptions'"); + } + + @HystrixCommand(fallbackMethod = "fallbackOverridesDefaultIgnoreExceptions") + public Object commandWithFallbackOverridesDefaultIgnoreExceptions(Class errorType) { + // isn't ignored, need to trigger fallback + throw new SpecificException(); + } + + @HystrixCommand(ignoreExceptions = SpecificException.class) + private Object fallbackOverridesDefaultIgnoreExceptions(Class errorType) { + if(errorType.equals(BadRequestException.class)){ + // isn't ignored because fallback doesn't specify this exception type in 'ignoreExceptions' + throw new BadRequestException("from 'fallbackOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + // something went wrong, this error is ignored because specified in the fallback's ignoreExceptions + throw new SpecificException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + } + + public static final class BadRequestException extends RuntimeException { + public BadRequestException() { + } + + public BadRequestException(String message) { + super(message); + } + } + + public static final class SpecificException extends RuntimeException { + public SpecificException() { + } + + public SpecificException(String message) { + super(message); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicErrorPropagationTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicErrorPropagationTest.java new file mode 100644 index 0000000..ffc310d --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicErrorPropagationTest.java @@ -0,0 +1,451 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.error; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import com.netflix.hystrix.exception.ExceptionNotWrappedByHystrix; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.*; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * Created by dmgcodevil + */ +public abstract class BasicErrorPropagationTest extends BasicHystrixTest { + + private static final String COMMAND_KEY = "getUserById"; + + private static final Map USERS; + + static { + USERS = new HashMap(); + USERS.put("1", new User("1", "user_1")); + USERS.put("2", new User("2", "user_2")); + USERS.put("3", new User("3", "user_3")); + } + + private UserService userService; + + @MockitoAnnotations.Mock + private FailoverService failoverService; + + protected abstract UserService createUserService(); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + userService = createUserService(); + userService.setFailoverService(failoverService); + } + + @Test(expected = BadRequestException.class) + public void testGetUserByBadId() throws NotFoundException { + try { + String badId = ""; + userService.getUserById(badId); + } finally { + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey(COMMAND_KEY); + // will not affect metrics + assertFalse(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and will not trigger fallback logic + verify(failoverService, never()).getDefUser(); + } + } + + @Test(expected = NotFoundException.class) + public void testGetNonExistentUser() throws NotFoundException { + try { + userService.getUserById("4"); // user with id 4 doesn't exist + } finally { + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey(COMMAND_KEY); + // will not affect metrics + assertFalse(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and will not trigger fallback logic + verify(failoverService, never()).getDefUser(); + } + } + + @Test // don't expect any exceptions because fallback must be triggered + public void testActivateUser() throws NotFoundException, ActivationException { + try { + userService.activateUser("1"); // this method always throws ActivationException + } finally { + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo activateUserCommand = getHystrixCommandByKey("activateUser"); + // will not affect metrics + assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + // and will not trigger fallback logic + verify(failoverService, atLeastOnce()).activate(); + } + } + + @Test(expected = RuntimeOperationException.class) + public void testBlockUser() throws NotFoundException, ActivationException, OperationException { + try { + userService.blockUser("1"); // this method always throws ActivationException + } finally { + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo activateUserCommand = getHystrixCommandByKey("blockUser"); + // will not affect metrics + assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_FAILURE)); + } + } + + @Test(expected = NotFoundException.class) + public void testPropagateCauseException() throws NotFoundException { + userService.deleteUser(""); + } + + @Test(expected = UserException.class) + public void testUserExceptionThrownFromCommand() { + userService.userFailureWithoutFallback(); + } + + @Test + public void testHystrixExceptionThrownFromCommand() { + try { + userService.timedOutWithoutFallback(); + } catch (HystrixRuntimeException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } catch (Throwable e) { + assertTrue("'HystrixRuntimeException' is expected exception.", false); + } + } + + @Test + public void testUserExceptionThrownFromFallback() { + try { + userService.userFailureWithFallback(); + } catch (UserException e) { + assertEquals(1, e.level); + } catch (Throwable e) { + assertTrue("'UserException' is expected exception.", false); + } + } + + @Test + public void testUserExceptionThrownFromFallbackCommand() { + try { + userService.userFailureWithFallbackCommand(); + } catch (UserException e) { + assertEquals(2, e.level); + } catch (Throwable e) { + assertTrue("'UserException' is expected exception.", false); + } + } + + @Test + public void testCommandAndFallbackErrorsComposition() { + try { + userService.commandAndFallbackErrorsComposition(); + } catch (HystrixFlowException e) { + assertEquals(UserException.class, e.commandException.getClass()); + assertEquals(UserException.class, e.fallbackException.getClass()); + + UserException commandException = (UserException) e.commandException; + UserException fallbackException = (UserException) e.fallbackException; + assertEquals(0, commandException.level); + assertEquals(1, fallbackException.level); + + + } catch (Throwable e) { + assertTrue("'HystrixFlowException' is expected exception.", false); + } + } + + @Test + public void testCommandWithFallbackThatFailsByTimeOut() { + try { + userService.commandWithFallbackThatFailsByTimeOut(); + } catch (HystrixRuntimeException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } catch (Throwable e) { + assertTrue("'HystrixRuntimeException' is expected exception.", false); + } + } + + @Test + public void testCommandWithNotWrappedExceptionAndNoFallback() { + try { + userService.throwNotWrappedCheckedExceptionWithoutFallback(); + fail(); + } catch (NotWrappedCheckedException e) { + // pass + } catch (Throwable e) { + fail("'NotWrappedCheckedException' is expected exception."); + }finally { + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("throwNotWrappedCheckedExceptionWithoutFallback"); + // record failure in metrics + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and will not trigger fallback logic + verify(failoverService, never()).activate(); + } + } + + @Test + public void testCommandWithNotWrappedExceptionAndFallback() { + try { + userService.throwNotWrappedCheckedExceptionWithFallback(); + } catch (NotWrappedCheckedException e) { + fail(); + } finally { + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("throwNotWrappedCheckedExceptionWithFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + verify(failoverService).activate(); + } + } + + public static class UserService { + + private FailoverService failoverService; + + public void setFailoverService(FailoverService failoverService) { + this.failoverService = failoverService; + } + + @HystrixCommand + public Object deleteUser(String id) throws NotFoundException { + throw new NotFoundException(""); + } + + @HystrixCommand( + commandKey = COMMAND_KEY, + ignoreExceptions = { + BadRequestException.class, + NotFoundException.class + }, + fallbackMethod = "fallback") + public User getUserById(String id) throws NotFoundException { + validate(id); + if (!USERS.containsKey(id)) { + throw new NotFoundException("user with id: " + id + " not found"); + } + return USERS.get(id); + } + + + @HystrixCommand( + ignoreExceptions = {BadRequestException.class, NotFoundException.class}, + fallbackMethod = "activateFallback") + public void activateUser(String id) throws NotFoundException, ActivationException { + validate(id); + if (!USERS.containsKey(id)) { + throw new NotFoundException("user with id: " + id + " not found"); + } + // always throw this exception + throw new ActivationException("user cannot be activate"); + } + + @HystrixCommand( + ignoreExceptions = {BadRequestException.class, NotFoundException.class}, + fallbackMethod = "blockUserFallback") + public void blockUser(String id) throws NotFoundException, OperationException { + validate(id); + if (!USERS.containsKey(id)) { + throw new NotFoundException("user with id: " + id + " not found"); + } + // always throw this exception + throw new OperationException("user cannot be blocked"); + } + + private User fallback(String id) { + return failoverService.getDefUser(); + } + + private void activateFallback(String id) { + failoverService.activate(); + } + + @HystrixCommand(ignoreExceptions = {RuntimeException.class}) + private void blockUserFallback(String id) { + throw new RuntimeOperationException("blockUserFallback has failed"); + } + + private void validate(String val) throws BadRequestException { + if (val == null || val.length() == 0) { + throw new BadRequestException("parameter cannot be null ot empty"); + } + } + + @HystrixCommand + void throwNotWrappedCheckedExceptionWithoutFallback() throws NotWrappedCheckedException { + throw new NotWrappedCheckedException(); + } + + @HystrixCommand(fallbackMethod = "voidFallback") + void throwNotWrappedCheckedExceptionWithFallback() throws NotWrappedCheckedException { + throw new NotWrappedCheckedException(); + } + + private void voidFallback(){ + failoverService.activate(); + } + + /*********************************************************************************/ + + @HystrixCommand + String userFailureWithoutFallback() throws UserException { + throw new UserException(); + } + + @HystrixCommand(commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1")}) + String timedOutWithoutFallback() { + return ""; + } + + /*********************************************************************************/ + + @HystrixCommand(fallbackMethod = "userFailureWithFallback_f_0") + String userFailureWithFallback() { + throw new UserException(); + } + + String userFailureWithFallback_f_0() { + throw new UserException(1); + } + + /*********************************************************************************/ + + @HystrixCommand(fallbackMethod = "userFailureWithFallbackCommand_f_0") + String userFailureWithFallbackCommand() { + throw new UserException(0); + } + + @HystrixCommand(fallbackMethod = "userFailureWithFallbackCommand_f_1") + String userFailureWithFallbackCommand_f_0() { + throw new UserException(1); + } + + @HystrixCommand + String userFailureWithFallbackCommand_f_1() { + throw new UserException(2); + } + + /*********************************************************************************/ + + @HystrixCommand(fallbackMethod = "commandAndFallbackErrorsComposition_f_0") + String commandAndFallbackErrorsComposition() { + throw new UserException(); + } + + + String commandAndFallbackErrorsComposition_f_0(Throwable commandError) { + throw new HystrixFlowException(commandError, new UserException(1)); + } + + /*********************************************************************************/ + + @HystrixCommand(fallbackMethod = "commandWithFallbackThatFailsByTimeOut_f_0") + String commandWithFallbackThatFailsByTimeOut() { + throw new UserException(0); + } + + @HystrixCommand(commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1")}) + String commandWithFallbackThatFailsByTimeOut_f_0() { + return ""; + } + } + + private class FailoverService { + public User getDefUser() { + return new User("def", "def"); + } + + public void activate() { + } + } + + // exceptions + private static class NotFoundException extends Exception { + private NotFoundException(String message) { + super(message); + } + } + + private static class BadRequestException extends RuntimeException { + private BadRequestException(String message) { + super(message); + } + } + + private static class ActivationException extends Exception { + private ActivationException(String message) { + super(message); + } + } + + private static class OperationException extends Throwable { + private OperationException(String message) { + super(message); + } + } + + private static class RuntimeOperationException extends RuntimeException { + private RuntimeOperationException(String message) { + super(message); + } + } + + private static class NotWrappedCheckedException extends Exception implements ExceptionNotWrappedByHystrix { + } + + static class UserException extends RuntimeException { + final int level; + + public UserException() { + this(0); + } + + public UserException(int level) { + this.level = level; + } + } + + static class HystrixFlowException extends RuntimeException { + final Throwable commandException; + final Throwable fallbackException; + + public HystrixFlowException(Throwable commandException, Throwable fallbackException) { + this.commandException = commandException; + this.fallbackException = fallbackException; + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicObservableErrorPropagationTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicObservableErrorPropagationTest.java new file mode 100644 index 0000000..cf6eff1 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicObservableErrorPropagationTest.java @@ -0,0 +1,260 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.error; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; +import rx.Observable; +import rx.observers.TestSubscriber; + +import java.util.HashMap; +import java.util.Map; + +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Created by dmgcodevil + */ +public abstract class BasicObservableErrorPropagationTest extends BasicHystrixTest { + + private static final String COMMAND_KEY = "getUserById"; + + private static final Map USERS; + + static { + USERS = new HashMap(); + USERS.put("1", new User("1", "user_1")); + USERS.put("2", new User("2", "user_2")); + USERS.put("3", new User("3", "user_3")); + } + + private UserService userService; + + @MockitoAnnotations.Mock + private FailoverService failoverService; + + protected abstract UserService createUserService(); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + userService = createUserService(); + userService.setFailoverService(failoverService); + } + + @Test + public void testGetUserByBadId() throws NotFoundException { + try { + TestSubscriber testSubscriber = new TestSubscriber(); + + String badId = ""; + userService.getUserById(badId).subscribe(testSubscriber); + + testSubscriber.assertError(BadRequestException.class); + } finally { + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey(COMMAND_KEY); + // will not affect metrics + assertFalse(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and will not trigger fallback logic + verify(failoverService, never()).getDefUser(); + } + } + + @Test + public void testGetNonExistentUser() throws NotFoundException { + try { + TestSubscriber testSubscriber = new TestSubscriber(); + + userService.getUserById("4").subscribe(testSubscriber); // user with id 4 doesn't exist + + testSubscriber.assertError(NotFoundException.class); + } finally { + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey(COMMAND_KEY); + // will not affect metrics + assertFalse(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and will not trigger fallback logic + verify(failoverService, never()).getDefUser(); + } + } + + @Test // don't expect any exceptions because fallback must be triggered + public void testActivateUser() throws NotFoundException, ActivationException { + try { + userService.activateUser("1").toBlocking().single(); // this method always throws ActivationException + } finally { + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo activateUserCommand = getHystrixCommandByKey("activateUser"); + // will not affect metrics + assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + // and will not trigger fallback logic + verify(failoverService, atLeastOnce()).activate(); + } + } + + @Test + public void testBlockUser() throws NotFoundException, ActivationException, OperationException { + try { + TestSubscriber testSubscriber = new TestSubscriber(); + + userService.blockUser("1").subscribe(testSubscriber); // this method always throws ActivationException + + testSubscriber.assertError(Throwable.class); + assertTrue(testSubscriber.getOnErrorEvents().size() == 1); + assertTrue(testSubscriber.getOnErrorEvents().get(0).getCause() instanceof OperationException); + } finally { + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo activateUserCommand = getHystrixCommandByKey("blockUser"); + // will not affect metrics + assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_FAILURE)); + } + } + + @Test + public void testPropagateCauseException() throws NotFoundException { + TestSubscriber testSubscriber = new TestSubscriber(); + + userService.deleteUser("").subscribe(testSubscriber); + + testSubscriber.assertError(NotFoundException.class); + } + + public static class UserService { + + private FailoverService failoverService; + + public void setFailoverService(FailoverService failoverService) { + this.failoverService = failoverService; + } + + @HystrixCommand + public Observable deleteUser(String id) throws NotFoundException { + return Observable.error(new NotFoundException("")); + } + + @HystrixCommand( + commandKey = COMMAND_KEY, + ignoreExceptions = { + BadRequestException.class, + NotFoundException.class + }, + fallbackMethod = "fallback") + public Observable getUserById(String id) throws NotFoundException { + validate(id); + if (!USERS.containsKey(id)) { + return Observable.error(new NotFoundException("user with id: " + id + " not found")); + } + return Observable.just(USERS.get(id)); + } + + + @HystrixCommand( + ignoreExceptions = {BadRequestException.class, NotFoundException.class}, + fallbackMethod = "activateFallback") + public Observable activateUser(String id) throws NotFoundException, ActivationException { + validate(id); + if (!USERS.containsKey(id)) { + return Observable.error(new NotFoundException("user with id: " + id + " not found")); + } + // always throw this exception + return Observable.error(new ActivationException("user cannot be activate")); + } + + @HystrixCommand( + ignoreExceptions = {BadRequestException.class, NotFoundException.class}, + fallbackMethod = "blockUserFallback") + public Observable blockUser(String id) throws NotFoundException, OperationException { + validate(id); + if (!USERS.containsKey(id)) { + return Observable.error(new NotFoundException("user with id: " + id + " not found")); + } + // always throw this exception + return Observable.error(new OperationException("user cannot be blocked")); + } + + private Observable fallback(String id) { + return failoverService.getDefUser(); + } + + private Observable activateFallback(String id) { + return failoverService.activate(); + } + + @HystrixCommand(ignoreExceptions = {RuntimeException.class}) + private Observable blockUserFallback(String id) { + return Observable.error(new RuntimeOperationException("blockUserFallback has failed")); + } + + private void validate(String val) throws BadRequestException { + if (val == null || val.length() == 0) { + throw new BadRequestException("parameter cannot be null ot empty"); + } + } + } + + private class FailoverService { + public Observable getDefUser() { + return Observable.just(new User("def", "def")); + } + + public Observable activate() { + return Observable.empty(); + } + } + + // exceptions + private static class NotFoundException extends Exception { + private NotFoundException(String message) { + super(message); + } + } + + private static class BadRequestException extends RuntimeException { + private BadRequestException(String message) { + super(message); + } + } + + private static class ActivationException extends Exception { + private ActivationException(String message) { + super(message); + } + } + + private static class OperationException extends Throwable { + private OperationException(String message) { + super(message); + } + } + + private static class RuntimeOperationException extends RuntimeException { + private RuntimeOperationException(String message) { + super(message); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicCommandFallbackTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicCommandFallbackTest.java new file mode 100644 index 0000000..fa73e94 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicCommandFallbackTest.java @@ -0,0 +1,461 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.fallback; + + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.command.AsyncResult; +import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.Domain; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import org.apache.commons.lang3.Validate; +import org.junit.Before; +import org.junit.Test; +import rx.Observable; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public abstract class BasicCommandFallbackTest extends BasicHystrixTest { + + private UserService userService; + + protected abstract UserService createUserService(); + + @Before + public void setUp() throws Exception { + userService = createUserService(); + } + + @Test + public void testGetUserAsyncWithFallback() throws ExecutionException, InterruptedException { + Future f1 = userService.getUserAsync(" ", "name: "); + + assertEquals("def", f1.get().getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("getUserAsync", command.getCommandKey().name()); + + // confirm that 'getUserAsync' command has failed + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback waw successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + + } + + @Test + public void testGetUserSyncWithFallback() { + User u1 = userService.getUserSync(" ", "name: "); + + assertEquals("def", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + + assertEquals("getUserSync", command.getCommandKey().name()); + // confirm that command has failed + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + + } + + + /** + * * **************************** * + * * * TEST FALLBACK COMMANDS * * + * * **************************** * + */ + @Test + public void testGetUserAsyncWithFallbackCommand() throws ExecutionException, InterruptedException { + Future f1 = userService.getUserAsyncFallbackCommand(" ", "name: "); + + assertEquals("def", f1.get().getName()); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo getUserAsyncFallbackCommand = getHystrixCommandByKey( + "getUserAsyncFallbackCommand"); + com.netflix.hystrix.HystrixInvokableInfo firstFallbackCommand = getHystrixCommandByKey("firstFallbackCommand"); + com.netflix.hystrix.HystrixInvokableInfo secondFallbackCommand = getHystrixCommandByKey("secondFallbackCommand"); + + assertEquals("getUserAsyncFallbackCommand", getUserAsyncFallbackCommand.getCommandKey().name()); + // confirm that command has failed + assertTrue(getUserAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // confirm that first fallback has failed + assertTrue(firstFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that second fallback was successful + assertTrue(secondFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetUserAsyncFallbackAsyncCommand() throws ExecutionException, InterruptedException { + Future f1 = userService.getUserAsyncFallbackAsyncCommand(" ", "name: "); + + assertEquals("def", f1.get().getName()); + + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo getUserAsyncFallbackAsyncCommand = getHystrixCommandByKey( + "getUserAsyncFallbackAsyncCommand"); + com.netflix.hystrix.HystrixInvokableInfo firstAsyncFallbackCommand = getHystrixCommandByKey("firstAsyncFallbackCommand"); + com.netflix.hystrix.HystrixInvokableInfo secondAsyncFallbackCommand = getHystrixCommandByKey("secondAsyncFallbackCommand"); + com.netflix.hystrix.HystrixInvokableInfo thirdAsyncFallbackCommand = getHystrixCommandByKey("thirdAsyncFallbackCommand"); + assertEquals("getUserAsyncFallbackAsyncCommand", getUserAsyncFallbackAsyncCommand.getCommandKey().name()); + // confirm that command has failed + assertTrue(getUserAsyncFallbackAsyncCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // confirm that first fallback has failed + assertTrue(firstAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that second fallback was successful + assertTrue(secondAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + + assertTrue(thirdAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(thirdAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetUserSyncWithFallbackCommand() { + User u1 = userService.getUserSyncFallbackCommand(" ", "name: "); + + assertEquals("def", u1.getName()); + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo getUserSyncFallbackCommand = getHystrixCommandByKey( + "getUserSyncFallbackCommand"); + com.netflix.hystrix.HystrixInvokableInfo firstFallbackCommand = getHystrixCommandByKey("firstFallbackCommand"); + com.netflix.hystrix.HystrixInvokableInfo secondFallbackCommand = getHystrixCommandByKey("secondFallbackCommand"); + + assertEquals("getUserSyncFallbackCommand", getUserSyncFallbackCommand.getCommandKey().name()); + // confirm that command has failed + assertTrue(getUserSyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // confirm that first fallback has failed + assertTrue(firstFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that second fallback was successful + assertTrue(secondFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testAsyncCommandWithAsyncFallbackCommand() throws ExecutionException, InterruptedException { + Future userFuture = userService.asyncCommandWithAsyncFallbackCommand("", ""); + User user = userFuture.get(); + assertEquals("def", user.getId()); + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo asyncCommandWithAsyncFallbackCommand = getHystrixCommandByKey("asyncCommandWithAsyncFallbackCommand"); + com.netflix.hystrix.HystrixInvokableInfo asyncFallbackCommand = getHystrixCommandByKey("asyncFallbackCommand"); + // confirm that command has failed + assertTrue(asyncCommandWithAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(asyncCommandWithAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + // and that second fallback was successful + assertTrue(asyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test(expected = FallbackDefinitionException.class) + public void testAsyncCommandWithAsyncFallback() { + userService.asyncCommandWithAsyncFallback("", ""); + } + + @Test(expected = FallbackDefinitionException.class) + public void testCommandWithWrongFallbackReturnType() { + userService.commandWithWrongFallbackReturnType("", ""); + } + + @Test(expected = FallbackDefinitionException.class) + public void testAsyncCommandWithWrongFallbackReturnType() { + userService.asyncCommandWithWrongFallbackReturnType("", ""); + } + + @Test(expected = FallbackDefinitionException.class) + public void testCommandWithWrongFallbackParams() { + userService.commandWithWrongFallbackParams("1", "2"); + } + + @Test(expected = FallbackDefinitionException.class) + public void testCommandWithFallbackReturnSuperType() { + userService.commandWithFallbackReturnSuperType("", ""); + } + + @Test + public void testCommandWithFallbackReturnSubType() { + User user = (User) userService.commandWithFallbackReturnSubType("", ""); + assertEquals("def", user.getName()); + } + + @Test + public void testCommandWithFallbackWithAdditionalParameter() { + User user = userService.commandWithFallbackWithAdditionalParameter("", ""); + assertEquals("def", user.getName()); + } + + @Test(expected = HystrixBadRequestException.class) + public void testCommandThrowsHystrixBadRequestExceptionWithNoCause() { + try { + userService.commandThrowsHystrixBadRequestExceptionWithNoCause(null, null); + } finally { + HystrixInvokableInfo asyncCommandWithAsyncFallbackCommand = getHystrixCommandByKey("commandThrowsHystrixBadRequestExceptionWithNoCause"); + assertFalse(asyncCommandWithAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + } + + @Test + public void testFallbackMissing(){ + try { + userService.getUserWithoutFallback(null, null); + } catch (Exception e) {} + + HystrixInvokableInfo command = getHystrixCommandByKey("getUserWithoutFallback"); + assertTrue("expected event: FALLBACK_MISSING", command.getExecutionEvents().contains(HystrixEventType.FALLBACK_MISSING)); + } + + public static class UserService { + + @HystrixCommand + public User getUserWithoutFallback(final String id, final String name) { + validate(id, name); + return new User(id, name); + } + + @HystrixCommand(fallbackMethod = "fallback") + public Future getUserAsync(final String id, final String name) { + validate(id, name); // validate logic can be inside and outside of AsyncResult#invoke method + return new AsyncResult() { + @Override + public User invoke() { + // validate(id, name); possible put validation logic here, in case of any exception a fallback method will be invoked + return new User(id, name + id); // it should be network call + } + }; + } + + @HystrixCommand(fallbackMethod = "fallback") + public User getUserSync(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + private User fallback(String id, String name) { + return new User("def", "def"); + } + + @HystrixCommand(fallbackMethod = "firstFallbackCommand") + public Future getUserAsyncFallbackCommand(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + validate(id, name); + return new User(id, name + id); // it should be network call + } + }; + } + + + @HystrixCommand(fallbackMethod = "firstFallbackCommand") + public User getUserSyncFallbackCommand(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + // FALLBACK COMMANDS METHODS: + // This fallback methods will be processed as hystrix commands + + @HystrixCommand(fallbackMethod = "secondFallbackCommand") + private User firstFallbackCommand(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + @HystrixCommand(fallbackMethod = "staticFallback") + private User secondFallbackCommand(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + @HystrixCommand(fallbackMethod = "firstAsyncFallbackCommand") + public Future getUserAsyncFallbackAsyncCommand(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + throw new RuntimeException("getUserAsyncFallbackAsyncCommand failed"); + } + }; + } + + @HystrixCommand(fallbackMethod = "secondAsyncFallbackCommand") + private Future firstAsyncFallbackCommand(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + throw new RuntimeException("firstAsyncFallbackCommand failed"); + } + }; + } + + @HystrixCommand(fallbackMethod = "thirdAsyncFallbackCommand") + private Future secondAsyncFallbackCommand(final String id, final String name, final Throwable e) { + return new AsyncResult() { + @Override + public User invoke() { + if ("firstAsyncFallbackCommand failed".equals(e.getMessage())) { + throw new RuntimeException("secondAsyncFallbackCommand failed"); + } + return new User(id, name + id); + } + }; + } + + @HystrixCommand(fallbackMethod = "fallbackWithAdditionalParam") + private Future thirdAsyncFallbackCommand(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + throw new RuntimeException("thirdAsyncFallbackCommand failed"); + } + }; + } + + private User fallbackWithAdditionalParam(final String id, final String name, final Throwable e) { + if (!"thirdAsyncFallbackCommand failed".equals(e.getMessage())) { + throw new RuntimeException("fallbackWithAdditionalParam failed"); + } + return new User("def", "def"); + } + + @HystrixCommand(fallbackMethod = "asyncFallbackCommand", commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100000") + }) + public Future asyncCommandWithAsyncFallbackCommand(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + validate(id, name); + return new User(id, name + id); // it should be network call + } + }; + } + + @HystrixCommand(fallbackMethod = "asyncFallback", commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100000") + }) + public Future asyncCommandWithAsyncFallback(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + validate(id, name); + return new User(id, name + id); // it should be network call + } + }; + } + + public Future asyncFallback(final String id, final String name) { + return Observable.just(new User("def", "def")).toBlocking().toFuture(); + } + + @HystrixCommand + public Future asyncFallbackCommand(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + return new User("def", "def"); // it should be network call + } + }; + } + + @HystrixCommand(fallbackMethod = "fallbackWithAdditionalParameter") + public User commandWithFallbackWithAdditionalParameter(final String id, final String name) { + validate(id, name); + return new User(id, name + id); + } + + public User fallbackWithAdditionalParameter(final String id, final String name, Throwable e) { + if (e == null) { + throw new RuntimeException("exception should be not null"); + } + return new User("def", "def"); + } + + @HystrixCommand(fallbackMethod = "fallbackWithStringReturnType") + public User commandWithWrongFallbackReturnType(final String id, final String name) { + validate(id, name); + return new User(id, name); + } + + @HystrixCommand(fallbackMethod = "fallbackWithStringReturnType") + public Future asyncCommandWithWrongFallbackReturnType(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + return new User("def", "def"); // it should be network call + } + }; + } + + @HystrixCommand(fallbackMethod = "fallbackWithoutParameters") + public User commandWithWrongFallbackParams(final String id, final String name) { + return new User(id, name); + } + + @HystrixCommand(fallbackMethod = "fallbackReturnSubTypeOfDomain") + public Domain commandWithFallbackReturnSubType(final String id, final String name) { + validate(id, name); + return new User(id, name); + } + + @HystrixCommand(fallbackMethod = "fallbackReturnSuperTypeOfDomain") + public User commandWithFallbackReturnSuperType(final String id, final String name) { + validate(id, name); + return new User(id, name); + } + + @HystrixCommand(fallbackMethod = "staticFallback") + public User commandThrowsHystrixBadRequestExceptionWithNoCause(final String id, final String name){ + throw new HystrixBadRequestException("invalid arguments"); + } + + private User fallbackReturnSubTypeOfDomain(final String id, final String name) { + return new User("def", "def"); + } + + private Domain fallbackReturnSuperTypeOfDomain(final String id, final String name) { + return new User("def", "def"); + } + + private String fallbackWithStringReturnType(final String id, final String name) { + return null; + } + + private User fallbackWithoutParameters() { + return null; + } + + private User staticFallback(String id, String name) { + return new User("def", "def"); + } + + private void validate(String id, String name) { + Validate.notBlank(id); + Validate.notBlank(name); + } + + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicDefaultFallbackTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicDefaultFallbackTest.java new file mode 100644 index 0000000..daa3cbd --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicDefaultFallbackTest.java @@ -0,0 +1,176 @@ +package com.netflix.hystrix.contrib.javanica.test.common.fallback; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import org.junit.Before; +import org.junit.Test; + + +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Created by dmgcodevil. + */ +public abstract class BasicDefaultFallbackTest extends BasicHystrixTest { + + private ServiceWithDefaultFallback serviceWithDefaultFallback; + private ServiceWithDefaultCommandFallback serviceWithDefaultCommandFallback; + + protected abstract ServiceWithDefaultFallback createServiceWithDefaultFallback(); + + protected abstract ServiceWithDefaultCommandFallback serviceWithDefaultCommandFallback(); + + @Before + public void setUp() throws Exception { + serviceWithDefaultFallback = createServiceWithDefaultFallback(); + serviceWithDefaultCommandFallback = serviceWithDefaultCommandFallback(); + } + + @Test + public void testClassScopeDefaultFallback() { + String res = serviceWithDefaultFallback.requestString(""); + assertEquals(ServiceWithDefaultFallback.DEFAULT_RESPONSE, res); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("requestString", command.getCommandKey().name()); + + // confirm that 'requestString' command has failed + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that default fallback waw successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testSpecificCommandFallbackOverridesDefault() { + Integer res = serviceWithDefaultFallback.commandWithSpecificFallback(""); + assertEquals(Integer.valueOf(res), res); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("commandWithSpecificFallback", command.getCommandKey().name()); + + // confirm that 'commandWithSpecificFallback' command has failed + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that default fallback waw successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + + @Test + public void testCommandScopeDefaultFallback() { + Long res = serviceWithDefaultFallback.commandWithDefaultFallback(1L); + assertEquals(Long.valueOf(0L), res); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("commandWithDefaultFallback", command.getCommandKey().name()); + + // confirm that 'requestString' command has failed + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that default fallback waw successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testClassScopeCommandDefaultFallback() { + String res = serviceWithDefaultCommandFallback.requestString(""); + assertEquals(ServiceWithDefaultFallback.DEFAULT_RESPONSE, res); + + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo requestStringCommand = getHystrixCommandByKey("requestString"); + com.netflix.hystrix.HystrixInvokableInfo fallback = getHystrixCommandByKey("classDefaultFallback"); + + assertEquals("requestString", requestStringCommand.getCommandKey().name()); + // confirm that command has failed + assertTrue(requestStringCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(requestStringCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + // confirm that fallback was successful + assertTrue(fallback.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testCommandScopeCommandDefaultFallback() { + Long res = serviceWithDefaultCommandFallback.commandWithDefaultFallback(1L); + assertEquals(Long.valueOf(0L), res); + + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + HystrixInvokableInfo requestStringCommand = getHystrixCommandByKey("commandWithDefaultFallback"); + com.netflix.hystrix.HystrixInvokableInfo fallback = getHystrixCommandByKey("defaultCommandFallback"); + + assertEquals("commandWithDefaultFallback", requestStringCommand.getCommandKey().name()); + // confirm that command has failed + assertTrue(requestStringCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(requestStringCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + // confirm that fallback was successful + assertTrue(fallback.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + // case when class level default fallback + @DefaultProperties(defaultFallback = "classDefaultFallback") + public static class ServiceWithDefaultFallback { + static final String DEFAULT_RESPONSE = "class_def"; + + @HystrixCommand + public String requestString(String str) { + throw new RuntimeException(); + } + + public String classDefaultFallback() { + return DEFAULT_RESPONSE; + } + + @HystrixCommand(defaultFallback = "defaultCommandFallback") + Long commandWithDefaultFallback(long l){ + throw new RuntimeException(); + } + + Long defaultCommandFallback(){ + return 0L; + } + + @HystrixCommand(fallbackMethod = "specificFallback") + Integer commandWithSpecificFallback(String str) { + throw new RuntimeException(); + } + + Integer specificFallback(String str) { + return 0; + } + } + + // class level default fallback is annotated with @HystrixCommand and should be executed as Hystrix command + @DefaultProperties(defaultFallback = "classDefaultFallback") + public static class ServiceWithDefaultCommandFallback { + static final String DEFAULT_RESPONSE = "class_def"; + + @HystrixCommand + public String requestString(String str) { + throw new RuntimeException(); + } + + @HystrixCommand + public String classDefaultFallback() { + return DEFAULT_RESPONSE; + } + + @HystrixCommand(defaultFallback = "defaultCommandFallback") + Long commandWithDefaultFallback(long l){ + throw new RuntimeException(); + } + + @HystrixCommand(fallbackMethod = "defaultCommandFallback") + Long defaultCommandFallback(){ + return 0L; + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicGenericFallbackTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicGenericFallbackTest.java new file mode 100644 index 0000000..f6f6d1b --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicGenericFallbackTest.java @@ -0,0 +1,306 @@ +package com.netflix.hystrix.contrib.javanica.test.common.fallback; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.helpers.MessageFormatter; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import rx.Completable; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertTrue; + +/** + * Created by dmgcodevil. + */ +@RunWith(JUnitParamsRunner.class) +public abstract class BasicGenericFallbackTest extends BasicHystrixTest { + + @ClassRule + public static final SpringClassRule SCR = new SpringClassRule(); + @Rule + public final SpringMethodRule springMethodRule = new SpringMethodRule(); + + protected abstract T createProxy(Class t); + + + public Object[] methodGenericDefinitionSuccess() { + return new Object[]{ + new Object[]{MethodGenericDefinitionSuccessCase0.class}, + new Object[]{MethodGenericDefinitionSuccessCase1.class}, + new Object[]{MethodGenericDefinitionSuccessCase2.class}, + new Object[]{MethodGenericDefinitionSuccessCase3.class}, + new Object[]{MethodGenericDefinitionSuccessCase4.class}, + new Object[]{MethodGenericDefinitionSuccessCase5.class}, + new Object[]{MethodGenericDefinitionSuccessCase6.class}, + new Object[]{MethodGenericDefinitionSuccessCase7.class}, + }; + } + + public Object[] methodGenericDefinitionFailure() { + return new Object[]{ + new Object[]{MethodGenericDefinitionFailureCase0.class}, + new Object[]{MethodGenericDefinitionFailureCase1.class}, + new Object[]{MethodGenericDefinitionFailureCase2.class}, + new Object[]{MethodGenericDefinitionFailureCase3.class}, + new Object[]{MethodGenericDefinitionFailureCase4.class}, + new Object[]{MethodGenericDefinitionFailureCase5.class}, + new Object[]{MethodGenericDefinitionFailureCase6.class}, + new Object[]{MethodGenericDefinitionFailureCase7.class}, + new Object[]{MethodGenericDefinitionFailureCase8.class}, + new Object[]{MethodGenericDefinitionFailureCase9.class}, + new Object[]{MethodGenericDefinitionFailureCase10.class}, + new Object[]{MethodGenericDefinitionFailureCase11.class}, + + }; + } + + public Object[] classGenericDefinitionSuccess() { + return new Object[]{ + new Object[]{ClassGenericDefinitionSuccessCase0.class}, + new Object[]{ClassGenericDefinitionSuccessCase1.class}, + }; + } + + + public Object[] classGenericDefinitionFailure() { + return new Object[]{ + new Object[]{ClassGenericDefinitionFailureCase0.class}, + new Object[]{ClassGenericDefinitionFailureCase1.class}, + }; + } + + @Test + @Parameters(method = "methodGenericDefinitionSuccess") + public void testMethodGenericDefinitionSuccess(Class type) { + testXKindGenericDefinitionSuccess(type); + } + + @Test(expected = FallbackDefinitionException.class) + @Parameters(method = "methodGenericDefinitionFailure") + public void testMethodGenericDefinitionFailure(Class type) { + Object p = createProxy(type); + executeCommand(p); + } + + @Test + @Parameters(method = "classGenericDefinitionSuccess") + public void testClassGenericDefinitionSuccess(Class type) { + testXKindGenericDefinitionSuccess(type); + } + + @Test(expected = FallbackDefinitionException.class) + @Parameters(method = "classGenericDefinitionFailure") + public void testClassGenericDefinitionFailure(Class type) { + Object p = createProxy(type); + executeCommand(p); + } + + private void testXKindGenericDefinitionSuccess(Class type) { + Object p = createProxy(type); + try { + executeCommand(p); + + HystrixInvokableInfo command = getHystrixCommandByKey("command"); + + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + + } catch (FallbackDefinitionException e) { + throw new AssertionError(MessageFormatter.format("Case class '{}' has failed. Reason:\n{}", type.getCanonicalName(), e).getMessage()); + } + } + + /* ====================================================================== */ + /* ===================== GENERIC METHOD DEFINITIONS ===================== */ + /* =========================== SUCCESS CASES ============================ */ + + public static class MethodGenericDefinitionSuccessCase0 { + @HystrixCommand(fallbackMethod = "fallback") + public T command() { throw new IllegalStateException(); } + private T fallback() { return null; } + } + + public static class MethodGenericDefinitionSuccessCase1 { + @HystrixCommand(fallbackMethod = "fallback") + public T command() { throw new IllegalStateException(); } + private T fallback() { return null; } + } + + public static class MethodGenericDefinitionSuccessCase2 { + @HystrixCommand(fallbackMethod = "fallback") + public T1 command() { throw new IllegalStateException(); } + private T1 fallback() { return null; } + } + + public static class MethodGenericDefinitionSuccessCase3 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return null; } + } + + public static class MethodGenericDefinitionSuccessCase4 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return null; } + } + + public static class MethodGenericDefinitionSuccessCase5 { + @HystrixCommand(fallbackMethod = "fallback") + public > GenericEntity command() { throw new IllegalStateException(); } + private > GenericEntity fallback() { return null; } + } + + public static class MethodGenericDefinitionSuccessCase6 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return new GenericEntity(); } + } + + public static class MethodGenericDefinitionSuccessCase7 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return null; } + } + + + /* ====================================================================== */ + /* ===================== GENERIC METHOD DEFINITIONS ===================== */ + /* =========================== FAILURE CASES ============================ */ + + public static class MethodGenericDefinitionFailureCase0 { + @HystrixCommand(fallbackMethod = "fallback") + public T command() { throw new IllegalStateException(); } + private String fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase1 { + @HystrixCommand(fallbackMethod = "fallback") + public T command() { throw new IllegalStateException(); } + private T fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase2 { + @HystrixCommand(fallbackMethod = "fallback") + public T command() { throw new IllegalStateException(); } + private T fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase3 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase4 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase5 { + @HystrixCommand(fallbackMethod = "fallback") + public > GenericEntity command() { throw new IllegalStateException(); } + private > GenericEntity fallback() { return null;} + } + + public static class MethodGenericDefinitionFailureCase6 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return new GenericEntity(); } + } + + public static class MethodGenericDefinitionFailureCase7 { + @HystrixCommand(fallbackMethod = "fallback") + public Set command() { throw new IllegalStateException(); } + private List fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase8 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity> command() { throw new IllegalStateException(); } + private GenericEntity> fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase9 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity>> command() { throw new IllegalStateException(); } + private GenericEntity>> fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase10 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return null; } + } + + public static class MethodGenericDefinitionFailureCase11 { + @HystrixCommand(fallbackMethod = "fallback") + public Completable command() { throw new IllegalStateException(); } + private void fallback() { return; } + } + + /* ====================================================================== */ + /* ===================== GENERIC CLASS DEFINITIONS =====+================ */ + /* =========================== SUCCESS CASES ============================ */ + + public static class ClassGenericDefinitionSuccessCase0 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return new GenericEntity(); } + } + + public static class ClassGenericDefinitionSuccessCase1 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return new GenericEntity(); } + } + + /* ====================================================================== */ + /* ===================== GENERIC CLASS DEFINITIONS =====+================ */ + /* =========================== FAILURE CASES ============================ */ + + public static class ClassGenericDefinitionFailureCase0 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return new GenericEntity(); } + } + + public static class ClassGenericDefinitionFailureCase1 { + @HystrixCommand(fallbackMethod = "fallback") + public GenericEntity command() { throw new IllegalStateException(); } + private GenericEntity fallback() { return new GenericEntity(); } + } + + public static class GenericEntity { + } + + private static Object executeCommand(Object proxy) { + try { + Method method = proxy.getClass().getDeclaredMethod("command"); + return method.invoke(proxy); + } catch (InvocationTargetException e) { + Throwable t = e.getCause(); + if (t instanceof FallbackDefinitionException) { + throw (FallbackDefinitionException) t; + } else throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java new file mode 100644 index 0000000..8af92d9 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java @@ -0,0 +1,344 @@ +/** + * Copyright 2016 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.contrib.javanica.test.common.observable; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.ObservableExecutionMode; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import org.apache.commons.lang3.StringUtils; +import org.junit.Before; +import org.junit.Test; +import rx.Completable; +import rx.Observable; +import rx.Observer; +import rx.Single; +import rx.Subscriber; +import rx.functions.Action1; +import rx.functions.Func0; + +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Created by dmgcodevil + */ +public abstract class BasicObservableTest extends BasicHystrixTest { + + + private UserService userService; + + protected abstract UserService createUserService(); + + @Before + public void setUp() throws Exception { + userService = createUserService(); + } + + @Test + public void testGetUserByIdSuccess() { + // blocking + Observable observable = userService.getUser("1", "name: "); + assertEquals("name: 1", observable.toBlocking().single().getName()); + + // non-blocking + // - this is a verbose anonymous inner-class approach and doesn't do assertions + Observable fUser = userService.getUser("1", "name: "); + fUser.subscribe(new Observer() { + + @Override + public void onCompleted() { + // nothing needed here + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(User v) { + System.out.println("onNext: " + v); + } + + }); + + Observable fs = userService.getUser("1", "name: "); + fs.subscribe(new Action1() { + + @Override + public void call(User user) { + assertEquals("name: 1", user.getName()); + } + }); + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getUser"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testGetCompletableUser(){ + userService.getCompletableUser("1", "name: "); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getCompletableUser"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testGetCompletableUserWithRegularFallback() { + Completable completable = userService.getCompletableUserWithRegularFallback(null, "name: "); + completable.toObservable().subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals("default_id", user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getCompletableUserWithRegularFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetCompletableUserWithRxFallback() { + Completable completable = userService.getCompletableUserWithRxFallback(null, "name: "); + completable.toObservable().subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals("default_id", user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getCompletableUserWithRxFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetSingleUser() { + final String id = "1"; + Single user = userService.getSingleUser(id, "name: "); + user.subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals(id, user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getSingleUser"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + @Test + public void testGetSingleUserWithRegularFallback(){ + Single user = userService.getSingleUserWithRegularFallback(null, "name: "); + user.subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals("default_id", user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getSingleUserWithRegularFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetSingleUserWithRxFallback(){ + Single user = userService.getSingleUserWithRxFallback(null, "name: "); + user.subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals("default_id", user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getSingleUserWithRxFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetUserWithRegularFallback() { + final User exUser = new User("def", "def"); + Observable userObservable = userService.getUserRegularFallback(" ", ""); + // blocking + assertEquals(exUser, userObservable.toBlocking().single()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getUserRegularFallback"); + // confirm that command has failed + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback was successful + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetUserWithRxFallback() { + final User exUser = new User("def", "def"); + + // blocking + assertEquals(exUser, userService.getUserRxFallback(" ", "").toBlocking().single()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getUserRxFallback"); + // confirm that command has failed + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback was successful + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetUserWithRxCommandFallback() { + final User exUser = new User("def", "def"); + + // blocking + Observable userObservable = userService.getUserRxCommandFallback(" ", ""); + assertEquals(exUser, userObservable.toBlocking().single()); + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserRxCommandFallback = getHystrixCommandByKey("getUserRxCommandFallback"); + com.netflix.hystrix.HystrixInvokableInfo rxCommandFallback = getHystrixCommandByKey("rxCommandFallback"); + // confirm that command has failed + assertTrue(getUserRxCommandFallback.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserRxCommandFallback.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + // and that fallback command was successful + assertTrue(rxCommandFallback.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + + public static class UserService { + + private User regularFallback(String id, String name) { + return new User("def", "def"); + } + + private Observable rxFallback(String id, String name) { + return Observable.just(new User("def", "def")); + } + + @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER) + private Observable rxCommandFallback(String id, String name, Throwable throwable) { + if (throwable instanceof GetUserException && "getUserRxCommandFallback has failed".equals(throwable.getMessage())) { + return Observable.just(new User("def", "def")); + } else { + throw new IllegalStateException(); + } + + } + + @HystrixCommand + public Observable getUser(final String id, final String name) { + validate(id, name, "getUser has failed"); + return createObservable(id, name); + } + + @HystrixCommand + public Completable getCompletableUser(final String id, final String name) { + validate(id, name, "getCompletableUser has failed"); + return createObservable(id, name).toCompletable(); + } + + @HystrixCommand(fallbackMethod = "completableUserRegularFallback") + public Completable getCompletableUserWithRegularFallback(final String id, final String name) { + return getCompletableUser(id, name); + } + + @HystrixCommand(fallbackMethod = "completableUserRxFallback") + public Completable getCompletableUserWithRxFallback(final String id, final String name) { + return getCompletableUser(id, name); + } + + public User completableUserRegularFallback(final String id, final String name) { + return new User("default_id", "default_name"); + } + + public Completable completableUserRxFallback(final String id, final String name) { + return Completable.fromCallable(new Func0() { + @Override + public User call() { + return new User("default_id", "default_name"); + } + }); + } + + @HystrixCommand + public Single getSingleUser(final String id, final String name) { + validate(id, name, "getSingleUser has failed"); + return createObservable(id, name).toSingle(); + } + + @HystrixCommand(fallbackMethod = "singleUserRegularFallback") + public Single getSingleUserWithRegularFallback(final String id, final String name) { + return getSingleUser(id, name); + } + + @HystrixCommand(fallbackMethod = "singleUserRxFallback") + public Single getSingleUserWithRxFallback(final String id, final String name) { + return getSingleUser(id, name); + } + + User singleUserRegularFallback(final String id, final String name) { + return new User("default_id", "default_name"); + } + + Single singleUserRxFallback(final String id, final String name) { + return createObservable("default_id", "default_name").toSingle(); + } + + @HystrixCommand(fallbackMethod = "regularFallback", observableExecutionMode = ObservableExecutionMode.LAZY) + public Observable getUserRegularFallback(final String id, final String name) { + validate(id, name, "getUser has failed"); + return createObservable(id, name); + } + + @HystrixCommand(fallbackMethod = "rxFallback") + public Observable getUserRxFallback(final String id, final String name) { + validate(id, name, "getUserRxFallback has failed"); + return createObservable(id, name); + } + + @HystrixCommand(fallbackMethod = "rxCommandFallback", observableExecutionMode = ObservableExecutionMode.LAZY) + public Observable getUserRxCommandFallback(final String id, final String name) { + validate(id, name, "getUserRxCommandFallback has failed"); + return createObservable(id, name); + } + + + private Observable createObservable(final String id, final String name) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber observer) { + try { + if (!observer.isUnsubscribed()) { + observer.onNext(new User(id, name + id)); + observer.onCompleted(); + } + } catch (Exception e) { + observer.onError(e); + } + } + }); + } + + private void validate(String id, String name, String errorMsg) { + if (StringUtils.isBlank(id) || StringUtils.isBlank(name)) { + throw new GetUserException(errorMsg); + } + } + + private static final class GetUserException extends RuntimeException { + public GetUserException(String message) { + super(message); + } + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/CacheTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/CacheTest.java new file mode 100644 index 0000000..b81499f --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/CacheTest.java @@ -0,0 +1,65 @@ +/** + * 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.contrib.javanica.test.spring.cache; + +import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCacheAspect; +import com.netflix.hystrix.contrib.javanica.test.common.cache.BasicCacheTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Test to check cache implementation based on JSR-107. + * + * @author dmgcodevil + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CacheTest.CacheTestConfig.class}) +public class CacheTest extends BasicCacheTest { + + @Autowired + private ApplicationContext applicationContext; + + + @Override + protected UserService createUserService() { + return applicationContext.getBean(UserService.class); + } + + /** + * Spring configuration. + */ + @Configurable + public static class CacheTestConfig { + @Bean + @Scope(value = "prototype") + public UserService userService() { + return new UserService(); + } + + @Bean + public HystrixCacheAspect hystrixCacheAspect() { + return new HystrixCacheAspect(); + } + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/collapser/CollapserTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/collapser/CollapserTest.java new file mode 100644 index 0000000..7865100 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/collapser/CollapserTest.java @@ -0,0 +1,57 @@ +/** + * 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.contrib.javanica.test.spring.collapser; + +import com.netflix.hystrix.contrib.javanica.test.common.collapser.BasicCollapserTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * This test covers "Request Collapsing" functionality. + *

+ * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Collapsing + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CollapserTest.CollapserTestConfig.class}) +public class CollapserTest extends BasicCollapserTest { + + @Autowired + private BasicCollapserTest.UserService userService; + + @Override + protected UserService createUserService() { + return userService; + } + + + /** + * Spring configuration. + */ + @Configurable + public static class CollapserTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/CommandTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/CommandTest.java new file mode 100644 index 0000000..29db664 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/CommandTest.java @@ -0,0 +1,72 @@ +/** + * 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.contrib.javanica.test.spring.command; + + +import com.netflix.hystrix.contrib.javanica.test.common.command.BasicCommandTest; +import com.netflix.hystrix.contrib.javanica.test.common.domain.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; + +/** + * This test covers "Hystrix command" functionality. + *

+ * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Synchronous-Execution + * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Asynchronous-Execution + */ +public abstract class CommandTest extends BasicCommandTest { + + @Autowired private BasicCommandTest.UserService userService; + @Autowired private BasicCommandTest.AdvancedUserService advancedUserService; + @Autowired private BasicCommandTest.GenericService genericUserService; + + @Override + protected BasicCommandTest.UserService createUserService() { + return userService; + } + + @Override + protected BasicCommandTest.AdvancedUserService createAdvancedUserServiceService() { + return advancedUserService; + } + + @Override + protected BasicCommandTest.GenericService createGenericUserService() { + return genericUserService; + } + + @Configurable + public static class CommandTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + + @Bean + public AdvancedUserService advancedUserService() { + return new AdvancedUserService(); + } + + @Bean + public GenericService genericUserService() { + return new GenericUserService(); + } + + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/cglib/CommandCGlibProxyTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/cglib/CommandCGlibProxyTest.java new file mode 100644 index 0000000..fa4a241 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/cglib/CommandCGlibProxyTest.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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.contrib.javanica.test.spring.command.cglib; + +import com.netflix.hystrix.contrib.javanica.test.spring.command.CommandTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CommandTest.CommandTestConfig.class}) +public class CommandCGlibProxyTest extends CommandTest { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/jdk/CommandJdkProxyTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/jdk/CommandJdkProxyTest.java new file mode 100644 index 0000000..3228e19 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/jdk/CommandJdkProxyTest.java @@ -0,0 +1,28 @@ +/** + * Copyright 2016 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.contrib.javanica.test.spring.command.jdk; + + +import com.netflix.hystrix.contrib.javanica.test.spring.command.CommandTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopJdkConfig; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopJdkConfig.class, CommandTest.CommandTestConfig.class}) +public class CommandJdkProxyTest extends CommandTest { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopCglibConfig.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopCglibConfig.java new file mode 100644 index 0000000..a604db1 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopCglibConfig.java @@ -0,0 +1,26 @@ +/** + * 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.contrib.javanica.test.spring.conf; + +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; + +@Configurable +@Import(SpringApplicationContext.class) +@EnableAspectJAutoProxy(proxyTargetClass = true) +public class AopCglibConfig { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopJdkConfig.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopJdkConfig.java new file mode 100644 index 0000000..d7d30aa --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopJdkConfig.java @@ -0,0 +1,26 @@ +/** + * 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.contrib.javanica.test.spring.conf; + +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; + +@Configurable +@Import(SpringApplicationContext.class) +@EnableAspectJAutoProxy +public class AopJdkConfig { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopLoadTimeWeavingConfig.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopLoadTimeWeavingConfig.java new file mode 100644 index 0000000..efa7222 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/AopLoadTimeWeavingConfig.java @@ -0,0 +1,28 @@ +/** + * 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.contrib.javanica.test.spring.conf; + +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.EnableLoadTimeWeaving; +import org.springframework.context.annotation.Import; + +@Configurable +@Import(SpringApplicationContext.class) +@EnableAspectJAutoProxy +@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED) +public class AopLoadTimeWeavingConfig { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/SpringApplicationContext.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/SpringApplicationContext.java new file mode 100644 index 0000000..cdf4172 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/SpringApplicationContext.java @@ -0,0 +1,32 @@ +/** + * 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.contrib.javanica.test.spring.conf; + +import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; + +@Configurable +@ComponentScan("com.netflix.hystrix.contrib.javanica.test.spring") +public class SpringApplicationContext { + + @Bean + public HystrixCommandAspect hystrixAspect() { + return new HystrixCommandAspect(); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/collapser/CollapserPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/collapser/CollapserPropertiesTest.java new file mode 100644 index 0000000..917b1a8 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/collapser/CollapserPropertiesTest.java @@ -0,0 +1,48 @@ +/** + * 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.contrib.javanica.test.spring.configuration.collapser; + +import com.netflix.hystrix.contrib.javanica.test.common.configuration.collapser.BasicCollapserPropertiesTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CollapserPropertiesTest.CollapserPropertiesTestConfig.class}) +public class CollapserPropertiesTest extends BasicCollapserPropertiesTest { + + @Autowired + private UserService userService; + + @Override + protected UserService createUserService() { + return userService; + } + + + @Configurable + public static class CollapserPropertiesTestConfig { + + @Bean + public BasicCollapserPropertiesTest.UserService userService() { + return new UserService(); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandDefaultPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandDefaultPropertiesTest.java new file mode 100644 index 0000000..1b0d8ac --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandDefaultPropertiesTest.java @@ -0,0 +1,36 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.configuration.command; + +import com.netflix.hystrix.contrib.javanica.test.common.configuration.command.BasicCommandDefaultPropertiesTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Created by dmgcodevil. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CommandDefaultPropertiesTest.Config.class}) +public class CommandDefaultPropertiesTest extends BasicCommandDefaultPropertiesTest { + + @Autowired + private Service service; + + @Override + protected Service createService() { + return service; + } + + @Configurable + public static class Config { + @Bean + @Scope(value = "prototype") + public BasicCommandDefaultPropertiesTest.Service service() { + return new BasicCommandDefaultPropertiesTest.Service(); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java new file mode 100644 index 0000000..55a8b74 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java @@ -0,0 +1,49 @@ +/** + * 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.contrib.javanica.test.spring.configuration.command; + +import com.netflix.hystrix.contrib.javanica.test.common.configuration.command.BasicCommandPropertiesTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CommandPropertiesTest.CommandPropertiesTestConfig.class}) +public class CommandPropertiesTest extends BasicCommandPropertiesTest { + + @Autowired + private BasicCommandPropertiesTest.UserService userService; + + @Override + protected UserService createUserService() { + return userService; + } + + + @Configurable + public static class CommandPropertiesTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/fallback/FallbackDefaultPropertiesTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/fallback/FallbackDefaultPropertiesTest.java new file mode 100644 index 0000000..b7680b7 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/fallback/FallbackDefaultPropertiesTest.java @@ -0,0 +1,31 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.configuration.fallback; + +import com.netflix.hystrix.contrib.javanica.test.common.configuration.fallback.BasicFallbackDefaultPropertiesTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, FallbackDefaultPropertiesTest.Config.class}) +public class FallbackDefaultPropertiesTest extends BasicFallbackDefaultPropertiesTest { + + @Autowired + private Service service; + + @Override + protected Service createService() { + return service; + } + + @Configurable + public static class Config { + @Bean + public BasicFallbackDefaultPropertiesTest.Service service() { + return new BasicFallbackDefaultPropertiesTest.Service(); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java new file mode 100644 index 0000000..c576770 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java @@ -0,0 +1,36 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.error; + +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicDefaultIgnoreExceptionsTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Created by dmgcodevil. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, DefaultIgnoreExceptionsTest.DefaultIgnoreExceptionsTestConfig.class}) +public class DefaultIgnoreExceptionsTest extends BasicDefaultIgnoreExceptionsTest { + + + @Autowired + private BasicDefaultIgnoreExceptionsTest.Service service; + + @Override + protected BasicDefaultIgnoreExceptionsTest.Service createService() { + return service; + } + + @Configurable + public static class DefaultIgnoreExceptionsTestConfig { + + @Bean + public BasicDefaultIgnoreExceptionsTest.Service userService() { + return new BasicDefaultIgnoreExceptionsTest.Service(); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultRaiseHystrixExceptionsTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultRaiseHystrixExceptionsTest.java new file mode 100644 index 0000000..8e65055 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultRaiseHystrixExceptionsTest.java @@ -0,0 +1,36 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.error; + +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicDefaultRaiseHystrixExceptionsTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; + +/** + * Created by Mike Cowan + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, DefaultRaiseHystrixExceptionsTest.DefaultRaiseHystrixExceptionsTestConfig.class}) +public class DefaultRaiseHystrixExceptionsTest extends BasicDefaultRaiseHystrixExceptionsTest { + + @Autowired + private BasicDefaultRaiseHystrixExceptionsTest.Service service; + + @Override + protected BasicDefaultRaiseHystrixExceptionsTest.Service createService() { + return service; + } + + @Configurable + public static class DefaultRaiseHystrixExceptionsTestConfig { + + @Bean + public BasicDefaultRaiseHystrixExceptionsTest.Service userService() { + return new BasicDefaultRaiseHystrixExceptionsTest.Service(); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ErrorPropagationTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ErrorPropagationTest.java new file mode 100644 index 0000000..8b66710 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ErrorPropagationTest.java @@ -0,0 +1,55 @@ +/** + * 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.contrib.javanica.test.spring.error; + + +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicErrorPropagationTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Test covers "Error Propagation" functionality. + * https://github.com/Netflix/Hystrix/wiki/How-To-Use#ErrorPropagation + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, ErrorPropagationTest.ErrorPropagationTestConfig.class}) +public class ErrorPropagationTest extends BasicErrorPropagationTest { + + + @Autowired + private BasicErrorPropagationTest.UserService userService; + + @Override + protected UserService createUserService() { + return userService; + } + + + @Configurable + public static class ErrorPropagationTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ObservableErrorPropagationTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ObservableErrorPropagationTest.java new file mode 100644 index 0000000..5ca77da --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ObservableErrorPropagationTest.java @@ -0,0 +1,55 @@ +/** + * 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.contrib.javanica.test.spring.error; + + +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicObservableErrorPropagationTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Test covers "Error Propagation" functionality. + * https://github.com/Netflix/Hystrix/wiki/How-To-Use#ErrorPropagation + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, ObservableErrorPropagationTest.ErrorPropagationTestConfig.class}) +public class ObservableErrorPropagationTest extends BasicObservableErrorPropagationTest { + + + @Autowired + private UserService userService; + + @Override + protected UserService createUserService() { + return userService; + } + + + @Configurable + public static class ErrorPropagationTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/CommandFallbackTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/CommandFallbackTest.java new file mode 100644 index 0000000..918c690 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/CommandFallbackTest.java @@ -0,0 +1,52 @@ +/** + * 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.contrib.javanica.test.spring.fallback; + +import com.netflix.hystrix.contrib.javanica.test.common.fallback.BasicCommandFallbackTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Test covers "Fallback" functionality. + *

+ * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Fallback + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CommandFallbackTest.CommandTestConfig.class}) +public class CommandFallbackTest extends BasicCommandFallbackTest { + + @Autowired + private UserService userService; + + @Override + protected BasicCommandFallbackTest.UserService createUserService() { + return userService; + } + + @Configurable + public static class CommandTestConfig { + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/DefaultFallbackTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/DefaultFallbackTest.java new file mode 100644 index 0000000..d4a0ef8 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/DefaultFallbackTest.java @@ -0,0 +1,47 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.fallback; + +import com.netflix.hystrix.contrib.javanica.test.common.fallback.BasicDefaultFallbackTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Created by dmgcodevil. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, DefaultFallbackTest.Config.class}) +public class DefaultFallbackTest extends BasicDefaultFallbackTest { + + @Autowired + private ServiceWithDefaultFallback serviceWithDefaultFallback; + @Autowired + private ServiceWithDefaultCommandFallback serviceWithDefaultCommandFallback; + + + @Override + protected ServiceWithDefaultFallback createServiceWithDefaultFallback() { + return serviceWithDefaultFallback; + } + + @Override + protected ServiceWithDefaultCommandFallback serviceWithDefaultCommandFallback() { + return serviceWithDefaultCommandFallback; + } + + @Configurable + public static class Config { + @Bean + public ServiceWithDefaultFallback serviceWithDefaultFallback() { + return new ServiceWithDefaultFallback(); + } + + @Bean + public ServiceWithDefaultCommandFallback serviceWithDefaultCommandFallback() { + return new ServiceWithDefaultCommandFallback(); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/GenericFallbackTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/GenericFallbackTest.java new file mode 100644 index 0000000..2c97b68 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/GenericFallbackTest.java @@ -0,0 +1,25 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.fallback; + +import com.netflix.hystrix.contrib.javanica.test.common.fallback.BasicGenericFallbackTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; + +/** + * Created by dmgcodevil. + */ +@ContextConfiguration(classes = {AopCglibConfig.class}) +public class GenericFallbackTest extends BasicGenericFallbackTest { + + @Autowired + private ApplicationContext applicationContext; + + @Override + protected T createProxy(Class t) { + AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory(); + return beanFactory.createBean(t); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/InheritedFallbackTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/InheritedFallbackTest.java new file mode 100644 index 0000000..62b05e5 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/InheritedFallbackTest.java @@ -0,0 +1,38 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.fallback; + +import com.netflix.hystrix.contrib.javanica.test.common.fallback.BasicCommandFallbackTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Created by dmgcodevil. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, InheritedFallbackTest.CommandTestConfig.class}) +public class InheritedFallbackTest extends BasicCommandFallbackTest { + + @Autowired + private UserService userService; + + @Override + protected BasicCommandFallbackTest.UserService createUserService() { + return userService; + } + + @Configurable + public static class CommandTestConfig { + @Bean + public UserService userService() { + return new SubClass(); + } + } + + public static class SubClass extends BasicCommandFallbackTest.UserService { + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/observable/ObservableTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/observable/ObservableTest.java new file mode 100644 index 0000000..b4f8b89 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/observable/ObservableTest.java @@ -0,0 +1,52 @@ +/** + * 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.contrib.javanica.test.spring.observable; + +import com.netflix.hystrix.contrib.javanica.test.common.observable.BasicObservableTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Test covers "Reactive Execution" functionality. + * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Reactive-Execution + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, ObservableTest.ObservableTestConfig.class}) +public class ObservableTest extends BasicObservableTest { + + @Autowired + private UserService userService; + + @Override + protected UserService createUserService() { + return userService; + } + + @Configurable + public static class ObservableTestConfig { + + @Bean + public BasicObservableTest.UserService userService() { + return new UserService(); + } + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodTest.java new file mode 100644 index 0000000..78f3847 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodTest.java @@ -0,0 +1,118 @@ +/** + * Copyright 2016 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.contrib.javanica.util; + +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Method; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Created by dmgcodevil. + */ +@RunWith(DataProviderRunner.class) +public class FallbackMethodTest { + + @Test + public void testGetExtendedFallback() throws NoSuchMethodException { + // given + Method command = Service.class.getDeclaredMethod("command", String.class, Integer.class); + // when + Method extFallback = MethodProvider.getInstance().getFallbackMethod(Service.class, command).getMethod(); + // then + assertParamsTypes(extFallback, String.class, Integer.class, Throwable.class); + } + + @Test + @DataProvider({"true", "false"}) + public void testGetFallbackForExtendedCommand(boolean extended) throws NoSuchMethodException { + // given + Method extFallback = Service.class.getDeclaredMethod("extCommand", String.class, Integer.class, Throwable.class); + // when + Method fallback = MethodProvider.getInstance().getFallbackMethod(Service.class, extFallback, extended).getMethod(); + // then + assertParamsTypes(fallback, String.class, Integer.class, Throwable.class); + } + + public void testGetFallbackForExtendedCommandV2() throws NoSuchMethodException { + // given + Method extFallback = Service.class.getDeclaredMethod("extCommandV2", String.class, Integer.class, Throwable.class); + // when + Method fallback = MethodProvider.getInstance().getFallbackMethod(Service.class, extFallback, true).getMethod(); + // then + assertParamsTypes(fallback, String.class, Integer.class); + } + + public void testGetFallbackForExtendedCommandV2_extendedParameterFalse() throws NoSuchMethodException { + // given + Method extFallback = Service.class.getDeclaredMethod("extCommandV2", String.class, Integer.class, Throwable.class); + // when + Method fallback = MethodProvider.getInstance().getFallbackMethod(Service.class, extFallback, false).getMethod(); + // then + assertNull(fallback); + } + + + private static void assertParamsTypes(Method method, Class... expected) { + assertEquals(expected.length, method.getParameterTypes().length); + Class[] actual = method.getParameterTypes(); + assertArrayEquals(expected, actual); + } + + private static class Common { + private String fallback(String s, Integer i) { + return null; + } + + private String fallbackV2(String s, Integer i) { + return null; + } + } + + private static class Service extends Common{ + + @HystrixCommand(fallbackMethod = "fallback") + public String command(String s, Integer i) { + return null; + } + + @HystrixCommand(fallbackMethod = "fallback") + public String extCommand(String s, Integer i, Throwable throwable) { + return null; + } + + + @HystrixCommand(fallbackMethod = "fallbackV2") + public String extCommandV2(String s, Integer i, Throwable throwable) { + return null; + } + + + public String fallback(String s, Integer i, Throwable throwable) { + return null; + } + + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodValidationTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodValidationTest.java new file mode 100644 index 0000000..a597439 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodValidationTest.java @@ -0,0 +1,181 @@ +/** + * Copyright 2016 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.contrib.javanica.util; + +import com.google.common.base.Throwables; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException; +import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Test; +import org.junit.runner.RunWith; +import rx.Observable; + +import java.lang.reflect.Method; +import java.util.concurrent.Future; + +/** + * Created by dmgcodevil. + */ +@RunWith(DataProviderRunner.class) +public class FallbackMethodValidationTest { + + + @DataProvider + public static Object[][] fail() { + // @formatter:off + return new Object[][]{ + // sync execution + {getMethod("commandReturnPlainTypeLong"), getMethod("fallbackReturnPlainTypeString")}, + {getMethod("commandReturnPlainTypeChild"), getMethod("fallbackReturnPlainTypeParent")}, + {getMethod("commandReturnGenericTypeParent"), getMethod("fallbackReturnGenericTypeChild")}, + {getMethod("commandReturnGenericTypeChild"), getMethod("fallbackReturnGenericTypeParent")}, + {getMethod("commandReturnGenericTypeChildParent"), getMethod("fallbackReturnGenericTypeParentChild")}, + {getMethod("commandReturnGenericTypeParentChild"), getMethod("fallbackReturnGenericTypeChildParent")}, + {getMethod("commandReturnGenericNestedTypeParentChildParent"), getMethod("commandReturnGenericNestedTypeParentParentParent")}, + + // async execution + {getMethod("commandReturnFutureParent"), getMethod("fallbackCommandReturnFutureChild")}, + {getMethod("commandReturnFutureParent"), getMethod("fallbackReturnFutureParent")}, + {getMethod("commandReturnFutureParent"), getMethod("fallbackReturnChild")}, + {getMethod("commandReturnParent"), getMethod("fallbackReturnFutureParent")}, + {getMethod("commandReturnParent"), getMethod("fallbackCommandReturnFutureParent")}, + + // observable execution + {getMethod("fallbackReturnObservableParent"), getMethod("fallbackReturnObservableChild")}, + {getMethod("fallbackReturnObservableParent"), getMethod("fallbackCommandReturnObservableChild")}, + {getMethod("fallbackReturnObservableParent"), getMethod("fallbackReturnChild")}, + {getMethod("commandReturnParent"), getMethod("fallbackReturnObservableParent")}, + {getMethod("commandReturnParent"), getMethod("fallbackCommandReturnObservableParent")}, + {getMethod("commandReturnParent"), getMethod("fallbackReturnObservableChild")}, + {getMethod("commandReturnParent"), getMethod("fallbackCommandReturnObservableChild")}, + }; + // @formatter:on + } + + @DataProvider + public static Object[][] success() { + // @formatter:off + return new Object[][]{ + // sync execution + {getMethod("commandReturnPlainTypeLong"), getMethod("fallbackReturnPlainTypeLong")}, + {getMethod("commandReturnPlainTypeParent"), getMethod("fallbackReturnPlainTypeChild")}, + {getMethod("commandReturnPlainTypeParent"), getMethod("fallbackReturnPlainTypeParent")}, + {getMethod("commandReturnGenericTypeChild"), getMethod("fallbackReturnGenericTypeChild")}, + {getMethod("commandReturnGenericNestedTypeParentChildParent"), getMethod("fallbackReturnGenericNestedTypeParentChildParent")}, + + + // async execution + {getMethod("commandReturnFutureParent"), getMethod("fallbackCommandReturnFutureParent")}, + {getMethod("commandReturnFutureParent"), getMethod("fallbackCommandReturnParent")}, + {getMethod("commandReturnFutureParent"), getMethod("fallbackReturnParent")}, + + // observable execution + {getMethod("commandReturnObservableParent"), getMethod("fallbackReturnObservableParent")}, + {getMethod("commandReturnObservableParent"), getMethod("fallbackCommandReturnObservableParent")}, + {getMethod("commandReturnObservableParent"), getMethod("fallbackReturnParent")}, + + }; + // @formatter:on + } + + @Test(expected = FallbackDefinitionException.class) + @UseDataProvider("fail") + public void testValidateBadFallbackReturnType(Method commandMethod, Method fallbackMethod) { + new FallbackMethod(fallbackMethod).validateReturnType(commandMethod); + } + + @UseDataProvider("success") + public void testValidateCorrectFallbackReturnType(Method commandMethod, Method fallbackMethod) { + new FallbackMethod(fallbackMethod).validateReturnType(commandMethod); + } + + private static Method getMethod(String name) { + try { + return Service.class.getDeclaredMethod(name); + } catch (NoSuchMethodException e) { + throw Throwables.propagate(e); + } + } + + // @formatter:off + private static class Service { + // Sync execution + public Parent commandReturnPlainTypeParent() {return null;} + public Child commandReturnPlainTypeChild() {return null;} + public Parent fallbackReturnPlainTypeParent() {return null;} + public Child fallbackReturnPlainTypeChild() {return null;} + public Long commandReturnPlainTypeLong() {return null;} + public Long fallbackReturnPlainTypeLong() {return null;} + public String fallbackReturnPlainTypeString() {return null;} + public GType commandReturnGenericTypeParent() {return null;} + public GType commandReturnGenericTypeChild() {return null;} + public GType fallbackReturnGenericTypeParent() {return null;} + public GType fallbackReturnGenericTypeChild() {return null;} + public GDoubleType commandReturnGenericTypeParentChild() {return null;} + public GDoubleType commandReturnGenericTypeChildParent() {return null;} + public GDoubleType fallbackReturnGenericTypeParentChild() {return null;} + public GDoubleType fallbackReturnGenericTypeChildParent() {return null;} + public GType>, Parent>>> commandReturnGenericNestedTypeParentChildParent() {return null;} + public GType>, Parent>>> commandReturnGenericNestedTypeParentParentParent() {return null;} + public GType>, Parent>>> fallbackReturnGenericNestedTypeParentChildParent() {return null;} + + // Async execution + Future commandReturnFutureParent() {return null;} + Parent commandReturnParent() {return null;} + + Parent fallbackReturnParent() {return null;} + Child fallbackReturnChild() {return null;} + Future fallbackReturnFutureParent() {return null;} + Future fallbackReturnFutureChild() {return null;} + + @HystrixCommand Parent fallbackCommandReturnParent() {return null;} + @HystrixCommand Child fallbackCommandReturnChild() {return null;} + @HystrixCommand Future fallbackCommandReturnFutureParent() {return null;} + @HystrixCommand Future fallbackCommandReturnFutureChild() {return null;} + + // Observable execution + Observable commandReturnObservableParent() {return null;} + + Observable fallbackReturnObservableParent() {return null;} + Observable fallbackReturnObservableChild() {return null;} + + @HystrixCommand Observable fallbackCommandReturnObservableParent() {return null;} + @HystrixCommand Observable fallbackCommandReturnObservableChild() {return null;} + } + // @formatter:on + + + + + private interface GType { + } + + private interface GDoubleType { + + } + + private static class Parent { + + } + + private static class Child extends Parent { + + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/GetMethodTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/GetMethodTest.java new file mode 100644 index 0000000..56dc8d3 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/GetMethodTest.java @@ -0,0 +1,51 @@ +package com.netflix.hystrix.contrib.javanica.util; + +import com.google.common.base.Optional; +import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Created by dmgcodevil. + */ +@RunWith(Parameterized.class) +public class GetMethodTest { + + private String methodName; + private Class[] parametersTypes; + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + { "foo", new Class[]{ String.class } }, + { "bar", new Class[]{ Integer.class } } + }); + } + + public GetMethodTest(String methodName, Class[] parametersTypes) { + this.methodName = methodName; + this.parametersTypes = parametersTypes; + } + + @Test + public void testGetMethodFoo(){ + Optional method = MethodProvider.getInstance().getMethod(C.class, methodName, parametersTypes); + + assertTrue(method.isPresent()); + assertEquals(methodName, method.get().getName()); + } + + + public static class A { void foo(String in) {} } + public static class B extends A { void bar(Integer in) {} } + public static class C extends B{ } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Child.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Child.java new file mode 100644 index 0000000..4fe8e20 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Child.java @@ -0,0 +1,22 @@ +/** + * Copyright 2016 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.contrib.javanica.util.bridge; + +/** + * Created by dmgcodevil. + */ +public class Child extends Parent { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterface.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterface.java new file mode 100644 index 0000000..158cedf --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterface.java @@ -0,0 +1,25 @@ +/** + * Copyright 2016 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.contrib.javanica.util.bridge; + +/** + * Created by dmgcodevil + */ +public interface GenericInterface { + + + R foo(P1 p1); +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterfaceImpl.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterfaceImpl.java new file mode 100644 index 0000000..da060bc --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterfaceImpl.java @@ -0,0 +1,37 @@ +/** + * Copyright 2016 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.contrib.javanica.util.bridge; + +/** + * Created by dmgcodevil + */ +public class GenericInterfaceImpl implements GenericInterface { + + + public Child foo(SubChild c) { + return null; + } + + @Override + public Child foo(Child c) { + return null; + } + + public Child foo(Parent c) { + return null; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Parent.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Parent.java new file mode 100644 index 0000000..7b0beab --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Parent.java @@ -0,0 +1,20 @@ +/** + * Copyright 2016 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.contrib.javanica.util.bridge; + + +public class Parent { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/SubChild.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/SubChild.java new file mode 100644 index 0000000..5227461 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/SubChild.java @@ -0,0 +1,22 @@ +/** + * Copyright 2016 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.contrib.javanica.util.bridge; + +/** + * Created by dmgcodevil. + */ +public class SubChild extends Child { +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/UnbridgeMethodTest.java b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/UnbridgeMethodTest.java new file mode 100644 index 0000000..70bfb83 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/UnbridgeMethodTest.java @@ -0,0 +1,64 @@ +/** + * Copyright 2016 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.contrib.javanica.util.bridge; + +import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; +import org.junit.Test; + +import java.io.IOException; +import java.lang.reflect.Method; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Created by dmgcodevil + */ +public class UnbridgeMethodTest { + + @Test + public void testUnbridgeFoo() throws NoSuchMethodException, IOException, ClassNotFoundException { + // given + Method bridgeMethod = getBridgeMethod(GenericInterfaceImpl.class, "foo"); + assertNotNull(bridgeMethod); + // when + Method genMethod = MethodProvider.getInstance().unbride(bridgeMethod, GenericInterfaceImpl.class); + // then + assertNotNull(bridgeMethod); + assertReturnType(Child.class, genMethod); + assertParamsTypes(genMethod, Child.class); + } + + private static Method getBridgeMethod(Class type, String methodName) { + for (Method method : type.getDeclaredMethods()) { + if (method.isBridge() && method.getName().equals(methodName)) { + return method; + } + } + return null; + } + + private static void assertReturnType(Class expected, Method method) { + assertEquals(expected, method.getReturnType()); + } + + private static void assertParamsTypes(Method method, Class... expected) { + assertEquals(expected.length, method.getParameterTypes().length); + Class[] actual = method.getParameterTypes(); + assertArrayEquals(expected, actual); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/resources/dummy.txt b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/resources/dummy.txt new file mode 100644 index 0000000..936ad77 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/resources/dummy.txt @@ -0,0 +1,16 @@ +==== + Copyright 2016 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. +==== + diff --git a/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/resources/log4j.properties b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/resources/log4j.properties new file mode 100644 index 0000000..a1465fa --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/resources/log4j.properties @@ -0,0 +1,27 @@ +# +# Copyright 2016 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. +# + +# Define the root logger with appender console +log4j.rootLogger = ERROR, CONSOLE + +# Define the console appender +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender + +# Define the layout for console appender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.conversionPattern=%m%n + +log4j.logger.com.netflix.hystrix.contrib.javanica=DEBUG diff --git a/Hystrix-master/hystrix-contrib/hystrix-junit/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-junit/build.gradle new file mode 100644 index 0000000..ea0d2ff --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-junit/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compileApi project(':hystrix-core') + compileApi "junit:junit:4.11" +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-junit/src/main/java/com/hystrix/junit/HystrixRequestContextRule.java b/Hystrix-master/hystrix-contrib/hystrix-junit/src/main/java/com/hystrix/junit/HystrixRequestContextRule.java new file mode 100644 index 0000000..eaf2580 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-junit/src/main/java/com/hystrix/junit/HystrixRequestContextRule.java @@ -0,0 +1,60 @@ +/** + * Copyright 2016 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.hystrix.junit; + +import com.netflix.hystrix.Hystrix; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.rules.ExternalResource; + +/** + * JUnit rule to be used to simplify tests that require a HystrixRequestContext. + * + * Example of usage: + * + *

+ *
+ *     @Rule
+ *     public HystrixRequestContextRule context = new HystrixRequestContextRule();
+ * 
+ *
+ * + */ +public final class HystrixRequestContextRule extends ExternalResource { + private HystrixRequestContext context; + + @Override + protected void before() { + this.context = HystrixRequestContext.initializeContext(); + Hystrix.reset(); + } + + @Override + protected void after() { + if (this.context != null) { + this.context.shutdown(); + this.context = null; + } + } + + public HystrixRequestContext context() { + return this.context; + } + + public void reset() { + after(); + before(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-junit/src/test/java/com/hystrix/junit/HystrixRequestContextRuleTest.java b/Hystrix-master/hystrix-contrib/hystrix-junit/src/test/java/com/hystrix/junit/HystrixRequestContextRuleTest.java new file mode 100644 index 0000000..7fb4b4b --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-junit/src/test/java/com/hystrix/junit/HystrixRequestContextRuleTest.java @@ -0,0 +1,23 @@ +package com.hystrix.junit; + +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.Rule; +import org.junit.Test; + +public final class HystrixRequestContextRuleTest { + @Rule + public HystrixRequestContextRule request = new HystrixRequestContextRule(); + + @Test + public void initsContext() { + MatcherAssert.assertThat(this.request.context(), CoreMatchers.notNullValue()); + } + + @Test + public void manuallyShutdownContextDontBreak() { + this.request.after(); + this.request.after(); + MatcherAssert.assertThat(this.request.context(), CoreMatchers.nullValue()); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/README.md b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/README.md new file mode 100644 index 0000000..b0c1623 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/README.md @@ -0,0 +1,62 @@ +# hystrix-metrics-event-stream-jaxrs + +This module is a JAX-RS implementation of [hystrix-metrics-event-stream](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream) module without any Servlet API dependency and exposes metrics in a [text/event-stream](https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events) formatted stream that continues as long as a client holds the connection. + + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-metrics-event-stream-jaxrs%22). + +Example for Maven ([lookup latest version](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-metrics-event-stream-jaxrs%22)): + +```xml + + com.netflix.hystrix + hystrix-metrics-event-stream-jaxrs + 1.6.0 + +``` +and for Ivy: + +```xml + +``` + +# Installation + +1) Include hystrix-metrics-event-stream-jaxrs*.jar in your classpath (such as /WEB-INF/lib). +2) Register `HystrixStreamFeature` in your `javax.ws.rs.core.Application` as shown below. + +```java + +public class HystrixStreamApplication extends Application{ + + @Override + public Set> getClasses() { + Set> clazzes = new HashSet>(); + clazzes.add(HystrixStreamFeature.class); + return clazzes; + } +} +``` + +3) Following end-points are available + * /hystrix.stream - Stream Hystrix Metrics + * /hystrix/utilization.stream - Stream Hystrix Utilization + * /hystrix/config.stream - Stream Hystrix configuration + * /hystrix/request.stream - Stream Hystrix SSE events + + + +# Test + +To test your installation you can use curl like this: + +``` +$ curl http://hostname:port/appname/hystrix.stream + +data: {"rollingCountFailure":0,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"rollingCountTimeout":0,"rollingCountExceptionsThrown":0,"rollingCountFallbackSuccess":0,"errorCount":0,"type":"HystrixCommand","propertyValue_circuitBreakerEnabled":true,"reportingHosts":1,"latencyTotal":{"0":0,"95":0,"99.5":0,"90":0,"25":0,"99":0,"75":0,"100":0,"50":0},"currentConcurrentExecutionCount":0,"rollingCountSemaphoreRejected":0,"rollingCountFallbackRejection":0,"rollingCountShortCircuited":0,"rollingCountResponsesFromCache":0,"propertyValue_circuitBreakerForceClosed":false,"name":"IdentityCookieAuthSwitchProfile","propertyValue_executionIsolationThreadPoolKeyOverride":"null","rollingCountSuccess":0,"propertyValue_requestLogEnabled":true,"requestCount":0,"rollingCountCollapsedRequests":0,"errorPercentage":0,"propertyValue_circuitBreakerSleepWindowInMilliseconds":5000,"latencyTotal_mean":0,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_circuitBreakerRequestVolumeThreshold":20,"propertyValue_circuitBreakerErrorThresholdPercentage":50,"propertyValue_executionIsolationStrategy":"THREAD","rollingCountFallbackFailure":0,"isCircuitBreakerOpen":false,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":20,"propertyValue_executionIsolationThreadTimeoutInMilliseconds":1000,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":10,"latencyExecute":{"0":0,"95":0,"99.5":0,"90":0,"25":0,"99":0,"75":0,"100":0,"50":0},"group":"IDENTITY","latencyExecute_mean":0,"propertyValue_requestCacheEnabled":true,"rollingCountThreadPoolRejected":0} + +data: {"rollingCountFailure":0,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"rollingCountTimeout":0,"rollingCountExceptionsThrown":0,"rollingCountFallbackSuccess":0,"errorCount":0,"type":"HystrixCommand","propertyValue_circuitBreakerEnabled":true,"reportingHosts":3,"latencyTotal":{"0":1,"95":1,"99.5":1,"90":1,"25":1,"99":1,"75":1,"100":1,"50":1},"currentConcurrentExecutionCount":0,"rollingCountSemaphoreRejected":0,"rollingCountFallbackRejection":0,"rollingCountShortCircuited":0,"rollingCountResponsesFromCache":0,"propertyValue_circuitBreakerForceClosed":false,"name":"CryptexDecrypt","propertyValue_executionIsolationThreadPoolKeyOverride":"null","rollingCountSuccess":1,"propertyValue_requestLogEnabled":true,"requestCount":1,"rollingCountCollapsedRequests":0,"errorPercentage":0,"propertyValue_circuitBreakerSleepWindowInMilliseconds":15000,"latencyTotal_mean":1,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_circuitBreakerRequestVolumeThreshold":60,"propertyValue_circuitBreakerErrorThresholdPercentage":150,"propertyValue_executionIsolationStrategy":"THREAD","rollingCountFallbackFailure":0,"isCircuitBreakerOpen":false,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":60,"propertyValue_executionIsolationThreadTimeoutInMilliseconds":3000,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":30000,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":30,"latencyExecute":{"0":0,"95":0,"99.5":0,"90":0,"25":0,"99":0,"75":0,"100":0,"50":0},"group":"CRYPTEX","latencyExecute_mean":0,"propertyValue_requestCacheEnabled":true,"rollingCountThreadPoolRejected":0} +``` + diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/build.gradle new file mode 100644 index 0000000..874881c --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/build.gradle @@ -0,0 +1,9 @@ +dependencies { + compileApi project(':hystrix-core') + compile project(':hystrix-serialization') + provided 'javax.ws.rs:javax.ws.rs-api:2.0.1' + testCompile 'junit:junit-dep:4.10' + testCompile 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:2.25.1' + testCompile 'org.glassfish.jersey.media:jersey-media-sse:2.25.1' + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStream.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStream.java new file mode 100644 index 0000000..b99e90b --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStream.java @@ -0,0 +1,49 @@ +/** + * Copyright 2016 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.contrib.metrics; + +import java.util.concurrent.atomic.AtomicInteger; + +import rx.Observable; + +/** + * @author justinjose28 + * + */ +public final class HystrixStream { + private final Observable sampleStream; + private final int pausePollerThreadDelayInMs; + private final AtomicInteger concurrentConnections; + + public HystrixStream(Observable sampleStream, int pausePollerThreadDelayInMs, AtomicInteger concurrentConnections) { + this.sampleStream = sampleStream; + this.pausePollerThreadDelayInMs = pausePollerThreadDelayInMs; + this.concurrentConnections = concurrentConnections; + } + + public Observable getSampleStream() { + return sampleStream; + } + + public int getPausePollerThreadDelayInMs() { + return pausePollerThreadDelayInMs; + } + + public AtomicInteger getConcurrentConnections() { + return concurrentConnections; + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStreamFeature.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStreamFeature.java new file mode 100644 index 0000000..f5f7883 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStreamFeature.java @@ -0,0 +1,42 @@ +/** + * Copyright 2016 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.contrib.metrics; + +import javax.ws.rs.core.Feature; +import javax.ws.rs.core.FeatureContext; + +import com.netflix.hystrix.contrib.metrics.controller.HystrixConfigSseController; +import com.netflix.hystrix.contrib.metrics.controller.HystrixMetricsStreamController; +import com.netflix.hystrix.contrib.metrics.controller.HystrixRequestEventsSseController; +import com.netflix.hystrix.contrib.metrics.controller.HystrixUtilizationSseController; + +/** + * @author justinjose28 + * + */ +public class HystrixStreamFeature implements Feature { + + @Override + public boolean configure(FeatureContext context) { + context.register(new HystrixMetricsStreamController()); + context.register(new HystrixUtilizationSseController()); + context.register(new HystrixRequestEventsSseController()); + context.register(new HystrixConfigSseController()); + context.register(HystrixStreamingOutputProvider.class); + return true; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStreamingOutputProvider.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStreamingOutputProvider.java new file mode 100644 index 0000000..5f2bf17 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/HystrixStreamingOutputProvider.java @@ -0,0 +1,104 @@ +/** + * Copyright 2016 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.contrib.metrics; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import rx.Subscriber; +import rx.Subscription; +import rx.schedulers.Schedulers; + +/** + * {@link MessageBodyWriter} implementation which handles serialization of HystrixStream + * + * + * @author justinjose28 + * + */ + +@Provider +public class HystrixStreamingOutputProvider implements MessageBodyWriter { + + private static final Logger LOGGER = LoggerFactory.getLogger(HystrixStreamingOutputProvider.class); + + @Override + public boolean isWriteable(Class t, Type gt, Annotation[] as, MediaType mediaType) { + return HystrixStream.class.isAssignableFrom(t); + } + + @Override + public long getSize(HystrixStream o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(HystrixStream o, Class t, Type gt, Annotation[] as, MediaType mediaType, MultivaluedMap httpHeaders, final OutputStream entity) throws IOException { + Subscription sampleSubscription = null; + final AtomicBoolean moreDataWillBeSent = new AtomicBoolean(true); + try { + + sampleSubscription = o.getSampleStream().observeOn(Schedulers.io()).subscribe(new Subscriber() { + @Override + public void onCompleted() { + LOGGER.error("HystrixSampleSseServlet: ({}) received unexpected OnCompleted from sample stream", getClass().getSimpleName()); + moreDataWillBeSent.set(false); + } + + @Override + public void onError(Throwable e) { + moreDataWillBeSent.set(false); + } + + @Override + public void onNext(String sampleDataAsString) { + if (sampleDataAsString != null) { + try { + entity.write(("data: " + sampleDataAsString + "\n\n").getBytes()); + entity.flush(); + } catch (IOException ioe) { + moreDataWillBeSent.set(false); + } + } + } + }); + + while (moreDataWillBeSent.get()) { + try { + Thread.sleep(o.getPausePollerThreadDelayInMs()); + } catch (InterruptedException e) { + moreDataWillBeSent.set(false); + } + } + } finally { + o.getConcurrentConnections().decrementAndGet(); + if (sampleSubscription != null && !sampleSubscription.isUnsubscribed()) { + sampleSubscription.unsubscribe(); + } + } + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/AbstractHystrixStreamController.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/AbstractHystrixStreamController.java new file mode 100644 index 0000000..146c22b --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/AbstractHystrixStreamController.java @@ -0,0 +1,85 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import rx.Observable; + +import com.netflix.hystrix.contrib.metrics.HystrixStream; +import com.netflix.hystrix.contrib.metrics.HystrixStreamingOutputProvider; + +/** + * @author justinjose28 + * + */ +public abstract class AbstractHystrixStreamController { + protected final Observable sampleStream; + + static final Logger logger = LoggerFactory.getLogger(AbstractHystrixStreamController.class); + + // wake up occasionally and check that poller is still alive. this value controls how often + protected static final int DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS = 500; + + private final int pausePollerThreadDelayInMs; + + protected AbstractHystrixStreamController(Observable sampleStream) { + this(sampleStream, DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS); + } + + protected AbstractHystrixStreamController(Observable sampleStream, int pausePollerThreadDelayInMs) { + this.sampleStream = sampleStream; + this.pausePollerThreadDelayInMs = pausePollerThreadDelayInMs; + } + + protected abstract int getMaxNumberConcurrentConnectionsAllowed(); + + protected abstract AtomicInteger getCurrentConnections(); + + /** + * Maintain an open connection with the client. On initial connection send latest data of each requested event type and subsequently send all changes for each requested event type. + * + * @return JAX-RS Response - Serialization will be handled by {@link HystrixStreamingOutputProvider} + */ + protected Response handleRequest() { + ResponseBuilder builder = null; + /* ensure we aren't allowing more connections than we want */ + int numberConnections = getCurrentConnections().get(); + int maxNumberConnectionsAllowed = getMaxNumberConcurrentConnectionsAllowed(); // may change at runtime, so look this up for each request + if (numberConnections >= maxNumberConnectionsAllowed) { + builder = Response.status(Status.SERVICE_UNAVAILABLE).entity("MaxConcurrentConnections reached: " + maxNumberConnectionsAllowed); + } else { + /* initialize response */ + builder = Response.status(Status.OK); + builder.header(HttpHeaders.CONTENT_TYPE, "text/event-stream;charset=UTF-8"); + builder.header(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate"); + builder.header("Pragma", "no-cache"); + getCurrentConnections().incrementAndGet(); + builder.entity(new HystrixStream(sampleStream, pausePollerThreadDelayInMs, getCurrentConnections())); + } + return builder.build(); + + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixConfigSseController.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixConfigSseController.java new file mode 100644 index 0000000..4347d04 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixConfigSseController.java @@ -0,0 +1,80 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; + +import rx.functions.Func1; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.config.HystrixConfiguration; +import com.netflix.hystrix.config.HystrixConfigurationStream; +import com.netflix.hystrix.contrib.metrics.HystrixStreamFeature; +import com.netflix.hystrix.serial.SerialHystrixConfiguration; + +/** + * Streams Hystrix config in text/event-stream format. + *

+ * Install by: + *

+ * 1) Including hystrix-metrics-event-stream-jaxrs-*.jar in your classpath. + *

+ * 2) Register {@link HystrixStreamFeature} in your {@link Application}. + *

+ * 3) Stream will be available at path /hystrix/config.stream + *

+ * + * @author justinjose28 + * + */ +@Path("/hystrix/config.stream") +public class HystrixConfigSseController extends AbstractHystrixStreamController { + + private static final AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public HystrixConfigSseController() { + super(HystrixConfigurationStream.getInstance().observe().map(new Func1() { + @Override + public String call(HystrixConfiguration hystrixConfiguration) { + return SerialHystrixConfiguration.toJsonString(hystrixConfiguration); + } + })); + } + + @GET + public Response getStream() { + return handleRequest(); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + + @Override + protected AtomicInteger getCurrentConnections() { + return concurrentConnections; + } + + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixMetricsStreamController.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixMetricsStreamController.java new file mode 100644 index 0000000..9fcc084 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixMetricsStreamController.java @@ -0,0 +1,78 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; + +import rx.Observable; +import rx.functions.Func1; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.contrib.metrics.HystrixStreamFeature; +import com.netflix.hystrix.metric.consumer.HystrixDashboardStream; +import com.netflix.hystrix.serial.SerialHystrixDashboardData; + +/** + * Streams Hystrix metrics in text/event-stream format. + *

+ * Install by: + *

+ * 1) Including hystrix-metrics-event-stream-jaxrs-*.jar in your classpath. + *

+ * 2) Register {@link HystrixStreamFeature} in your {@link Application}. + *

+ * 3) Stream will be available at path /hystrix.stream + *

+ * + * @author justinjose28 + * + */ +@Path("/hystrix.stream") +public class HystrixMetricsStreamController extends AbstractHystrixStreamController { + + private static final AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public HystrixMetricsStreamController() { + super(HystrixDashboardStream.getInstance().observe().concatMap(new Func1>() { + @Override + public Observable call(HystrixDashboardStream.DashboardData dashboardData) { + return Observable.from(SerialHystrixDashboardData.toMultipleJsonStrings(dashboardData)); + } + })); + } + + @GET + public Response getStream() { + return handleRequest(); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + @Override + protected AtomicInteger getCurrentConnections() { + return concurrentConnections; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixRequestEventsSseController.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixRequestEventsSseController.java new file mode 100644 index 0000000..2ada20b --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixRequestEventsSseController.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; + +import rx.functions.Func1; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.contrib.metrics.HystrixStreamFeature; +import com.netflix.hystrix.metric.HystrixRequestEvents; +import com.netflix.hystrix.metric.HystrixRequestEventsStream; +import com.netflix.hystrix.serial.SerialHystrixRequestEvents; + +/** + * Resource that writes SSE JSON every time a request is made + * + *

+ * Install by: + *

+ * 1) Including hystrix-metrics-event-stream-jaxrs-*.jar in your classpath. + *

+ * 2) Register {@link HystrixStreamFeature} in your {@link Application}. + *

+ * 3) Stream will be available at path /hystrix/request.stream + *

+ * + * @author justinjose28 + * + */ +@Path("/hystrix/request.stream") +public class HystrixRequestEventsSseController extends AbstractHystrixStreamController { + + private static final AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public HystrixRequestEventsSseController() { + super(HystrixRequestEventsStream.getInstance().observe().map(new Func1() { + @Override + public String call(HystrixRequestEvents requestEvents) { + return SerialHystrixRequestEvents.toJsonString(requestEvents); + } + })); + } + + @GET + public Response getStream() { + return handleRequest(); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + + @Override + protected AtomicInteger getCurrentConnections() { + return concurrentConnections; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixUtilizationSseController.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixUtilizationSseController.java new file mode 100644 index 0000000..50e8997 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/main/java/com/netflix/hystrix/contrib/metrics/controller/HystrixUtilizationSseController.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; + +import rx.functions.Func1; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.contrib.metrics.HystrixStreamFeature; +import com.netflix.hystrix.metric.sample.HystrixUtilization; +import com.netflix.hystrix.metric.sample.HystrixUtilizationStream; +import com.netflix.hystrix.serial.SerialHystrixUtilization; + +/** + * Streams Hystrix config in text/event-stream format. + *

+ * Install by: + *

+ * 1) Including hystrix-metrics-event-stream-jaxrs-*.jar in your classpath. + *

+ * 2) Register {@link HystrixStreamFeature} in your {@link Application}. + *

+ * 3) Stream will be available at path /hystrix/utilization.stream + *

+ * + * @author justinjose28 + * + */ +@Path("/hystrix/utilization.stream") +public class HystrixUtilizationSseController extends AbstractHystrixStreamController { + + private static final AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public HystrixUtilizationSseController() { + super(HystrixUtilizationStream.getInstance().observe().map(new Func1() { + @Override + public String call(HystrixUtilization hystrixUtilization) { + return SerialHystrixUtilization.toJsonString(hystrixUtilization); + } + })); + } + + @GET + public Response getStream() { + return handleRequest(); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + + @Override + protected AtomicInteger getCurrentConnections() { + return concurrentConnections; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystricsMetricsControllerTest.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystricsMetricsControllerTest.java new file mode 100644 index 0000000..8baf327 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystricsMetricsControllerTest.java @@ -0,0 +1,202 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.ServiceUnavailableException; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; + +import org.apache.commons.configuration.SystemConfiguration; +import org.glassfish.jersey.media.sse.EventInput; +import org.glassfish.jersey.media.sse.InboundEvent; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.junit.Assert; +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.contrib.metrics.HystrixStreamFeature; + +/** + * @author justinjose28 + * + */ +@Path("/hystrix") +public class HystricsMetricsControllerTest extends JerseyTest { + protected static final AtomicInteger requestCount = new AtomicInteger(0); + + @POST + @Path("/command") + @Consumes(APPLICATION_JSON) + public void command() throws Exception { + TestHystrixCommand command = new TestHystrixCommand(); + command.execute(); + } + + @Override + protected Application configure() { + int port = 0; + try { + final ServerSocket socket = new ServerSocket(0); + port = socket.getLocalPort(); + socket.close(); + } catch (IOException e1) { + throw new RuntimeException("Failed to find port to start test server"); + } + set(TestProperties.CONTAINER_PORT, port); + try { + SystemConfiguration.setSystemProperties("test.properties"); + } catch (Exception e) { + throw new RuntimeException("Failed to load config file"); + } + return new ResourceConfig(HystricsMetricsControllerTest.class, HystrixStreamFeature.class); + } + + protected String getPath() { + return "hystrix.stream"; + } + + protected boolean isStreamValid(String data) { + return data.contains("\"type\":\"HystrixThreadPool\"") && data.contains("\"currentCompletedTaskCount\":" + requestCount.get()); + } + + @Test + public void testInfiniteStream() throws Exception { + executeHystrixCommand(); // Execute a Hystrix command so that metrics are initialized. + EventInput stream = getStream(); // Invoke Stream API which returns a steady stream output. + try { + validateStream(stream, 1000); // Validate the stream. + System.out.println("Validated Stream Output 1"); + executeHystrixCommand(); // Execute Hystrix Command again so that request count is updated. + validateStream(stream, 1000); // Stream should show updated request count + System.out.println("Validated Stream Output 2"); + } finally { + if (stream != null) { + stream.close(); + } + } + } + + @Test + public void testConcurrency() throws Exception { + executeHystrixCommand(); // Execute a Hystrix command so that metrics are initialized. + List streamList = new ArrayList(); + try { + // Fire 3 requests, validate their responses and hold these connections. + for (int i = 0; i < 3; i++) { + EventInput stream = getStream(); + System.out.println("Received Response for Request#" + (i + 1)); + streamList.add(stream); + validateStream(stream, 1000); + System.out.println("Validated Response#" + (i + 1)); + } + + // Fourth request should fail since max configured connection is 3. + try { + streamList.add(getStreamFailFast()); + Assert.fail("Expected 'ServiceUnavailableException' but, request went through."); + } catch (ServiceUnavailableException e) { + System.out.println("Got ServiceUnavailableException as expected."); + } + + // Close one of the connections + streamList.get(0).close(); + streamList.remove(0); + + // Try again after closing one of the connections. This request should go through. + EventInput eventInput = getStream(); + streamList.add(eventInput); + validateStream(eventInput, 1000); + } finally { + for (EventInput stream : streamList) { + if (stream != null) { + stream.close(); + } + } + } + + } + + private void executeHystrixCommand() throws Exception { + Response response = target("hystrix/command").request().post(null); + assertEquals(204, response.getStatus()); + System.out.println("Hystrix Command ran successfully."); + requestCount.incrementAndGet(); + } + + private EventInput getStream() throws Exception { + long timeElapsed = System.currentTimeMillis(); + while (System.currentTimeMillis() - timeElapsed < 3000) { + try { + return getStreamFailFast(); + } catch (Exception e) { + + } + } + fail("Not able to connect to Stream end point"); + return null; + } + + private EventInput getStreamFailFast() throws Exception { + return target(getPath()).request().get(EventInput.class); + } + + private void validateStream(EventInput eventInput, long waitTime) { + long timeElapsed = System.currentTimeMillis(); + while (!eventInput.isClosed() && System.currentTimeMillis() - timeElapsed < waitTime) { + final InboundEvent inboundEvent = eventInput.read(); + if (inboundEvent == null) { + Assert.fail("Failed while verifying stream. Looks like connection has been closed."); + break; + } + String data = inboundEvent.readData(String.class); + System.out.println(data); + if (isStreamValid(data)) { + return; + } + } + Assert.fail("Failed while verifying stream"); + } + + public static class TestHystrixCommand extends HystrixCommand { + + protected TestHystrixCommand() { + super(HystrixCommandGroupKey.Factory.asKey("test")); + } + + @Override + protected Void run() throws Exception { + return null; + } + + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystrixConfigControllerTest.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystrixConfigControllerTest.java new file mode 100644 index 0000000..dcab350 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystrixConfigControllerTest.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +/** + * @author justinjose28 + * + */ +public class HystrixConfigControllerTest extends HystricsMetricsControllerTest { + + @Override + protected String getPath() { + return "hystrix/config.stream"; + } + + @Override + protected boolean isStreamValid(String data) { + return data.contains("\"type\":\"HystrixConfig\""); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystrixUtilizationControllerTest.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystrixUtilizationControllerTest.java new file mode 100644 index 0000000..c4b9250 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/HystrixUtilizationControllerTest.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +/** + * @author justinjose28 + * + */ +public class HystrixUtilizationControllerTest extends HystricsMetricsControllerTest { + + @Override + protected String getPath() { + return "hystrix/utilization.stream"; + } + + @Override + protected boolean isStreamValid(String data) { + return data.contains("\"type\":\"HystrixUtilization\""); + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/StreamingOutputProviderTest.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/StreamingOutputProviderTest.java new file mode 100644 index 0000000..a259d4e --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/java/com/netflix/hystrix/contrib/metrics/controller/StreamingOutputProviderTest.java @@ -0,0 +1,225 @@ +/** + * Copyright 2016 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.contrib.metrics.controller; + +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +import com.netflix.hystrix.contrib.metrics.HystrixStream; +import com.netflix.hystrix.contrib.metrics.HystrixStreamingOutputProvider; + +public class StreamingOutputProviderTest { + + private final Observable streamOfOnNexts = Observable.interval(100, TimeUnit.MILLISECONDS).map(new Func1() { + @Override + public String call(Long timestamp) { + return "test-stream"; + } + }); + + private final Observable streamOfOnNextThenOnError = Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + Thread.sleep(100); + subscriber.onNext("test-stream"); + Thread.sleep(100); + subscriber.onError(new RuntimeException("stream failure")); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + }).subscribeOn(Schedulers.computation()); + + private final Observable streamOfOnNextThenOnCompleted = Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + Thread.sleep(100); + subscriber.onNext("test-stream"); + Thread.sleep(100); + subscriber.onCompleted(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + }).subscribeOn(Schedulers.computation()); + + private AbstractHystrixStreamController sse = new AbstractHystrixStreamController(streamOfOnNexts) { + private final AtomicInteger concurrentConnections = new AtomicInteger(0); + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return 2; + } + @Override + protected AtomicInteger getCurrentConnections() { + return concurrentConnections; + } + + }; + + @Test + public void concurrencyTest() throws Exception { + + Response resp = sse.handleRequest(); + assertEquals(200, resp.getStatus()); + assertEquals("text/event-stream;charset=UTF-8", resp.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + assertEquals("no-cache, no-store, max-age=0, must-revalidate", resp.getHeaders().getFirst(HttpHeaders.CACHE_CONTROL)); + assertEquals("no-cache", resp.getHeaders().getFirst("Pragma")); + + resp = sse.handleRequest(); + assertEquals(200, resp.getStatus()); + + resp = sse.handleRequest(); + assertEquals(503, resp.getStatus()); + assertEquals("MaxConcurrentConnections reached: " + sse.getMaxNumberConcurrentConnectionsAllowed(), resp.getEntity()); + + sse.getCurrentConnections().decrementAndGet(); + + resp = sse.handleRequest(); + assertEquals(200, resp.getStatus()); + } + + @Test + public void testInfiniteOnNextStream() throws Exception { + final PipedInputStream is = new PipedInputStream(); + final PipedOutputStream os = new PipedOutputStream(is); + final AtomicInteger writes = new AtomicInteger(0); + final HystrixStream stream = new HystrixStream(streamOfOnNexts, 100, new AtomicInteger(1)); + Thread streamingThread = startStreamingThread(stream, os); + verifyStream(is, writes); + Thread.sleep(1000); // Let the provider stream for some time. + streamingThread.interrupt(); // Stop streaming + + os.close(); + is.close(); + + System.out.println("Total lines:" + writes.get()); + assertTrue(writes.get() >= 9); // Observable is configured to emit events in every 100 ms. So expect at least 9 in a second. + + // Provider is expected to decrement connection count when streaming process is terminated. + assertTrue(hasNoMoreConcurrentConnections(stream.getConcurrentConnections(), 200, 10, TimeUnit.MILLISECONDS)); + } + + @Test + public void testOnError() throws Exception { + testStreamOnce(streamOfOnNextThenOnError); + } + + @Test + public void testOnComplete() throws Exception { + testStreamOnce(streamOfOnNextThenOnCompleted); + } + + // as the concurrentConnections count is decremented asynchronously, we need to potentially give the check a little bit of time + private static boolean hasNoMoreConcurrentConnections(AtomicInteger concurrentConnectionsCount, long waitDuration, long pollInterval, TimeUnit timeUnit) throws InterruptedException { + long period = (pollInterval > waitDuration) ? waitDuration : pollInterval; + + for (long i = 0; i < waitDuration; i += period) { + if (concurrentConnectionsCount.get() == 0) { + return true; + } + Thread.sleep(timeUnit.toMillis(period)); + } + + return false; + } + + private void testStreamOnce(Observable observable) throws Exception { + final PipedInputStream is = new PipedInputStream(); + final PipedOutputStream os = new PipedOutputStream(is); + final AtomicInteger writes = new AtomicInteger(0); + final HystrixStream stream = new HystrixStream(observable, 100, new AtomicInteger(1)); + startStreamingThread(stream, os); + verifyStream(is, writes); + Thread.sleep(1000); + + os.close(); + is.close(); + + System.out.println("Total lines:" + writes.get()); + assertTrue(writes.get() == 1); + + assertTrue(hasNoMoreConcurrentConnections(stream.getConcurrentConnections(), 200, 10, TimeUnit.MILLISECONDS)); + } + + private static Thread startStreamingThread(final HystrixStream stream, final OutputStream outputSteam) { + Thread th1 = new Thread(new Runnable() { + @Override + public void run() { + try { + final HystrixStreamingOutputProvider provider = new HystrixStreamingOutputProvider(); + provider.writeTo(stream, null, null, null, null, null, outputSteam); + } catch (IOException e) { + fail(e.getMessage()); + } + } + }); + th1.start(); + return th1; + } + + private static void verifyStream(final InputStream is, final AtomicInteger lineCount) { + Thread th2 = new Thread(new Runnable() { + public void run() { + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = br.readLine()) != null) { + if (!"".equals(line)) { + System.out.println(line); + lineCount.incrementAndGet(); + } + } + } catch (IOException e) { + fail("Failed while verifying streaming output.Stacktrace:" + e.getMessage()); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + fail("Failed while verifying streaming output.Stacktrace:" + e.getMessage()); + } + } + } + + } + }); + th2.start(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/resources/test.properties b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/resources/test.properties new file mode 100644 index 0000000..b0d7645 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream-jaxrs/src/test/resources/test.properties @@ -0,0 +1,4 @@ +hystrix.stream.utilization.intervalInMilliseconds=10 +hystrix.stream.dashboard.intervalInMilliseconds=10 +hystrix.stream.config.intervalInMilliseconds=10 +hystrix.config.stream.maxConcurrentConnections=3 diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/README.md b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/README.md new file mode 100644 index 0000000..10c7c40 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/README.md @@ -0,0 +1,148 @@ +# hystrix-metrics-event-stream + +This module exposes metrics in a [text/event-stream](https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events) formatted stream that continues as long as a client holds the connection. + +Each HystrixCommand instance will emit data such as this (without line breaks): + +```json +data: { + "type": "HystrixCommand", + "name": "PlaylistGet", + "group": "PlaylistGet", + "currentTime": 1355239617628, + "isCircuitBreakerOpen": false, + "errorPercentage": 0, + "errorCount": 0, + "requestCount": 121, + "rollingCountCollapsedRequests": 0, + "rollingCountExceptionsThrown": 0, + "rollingCountFailure": 0, + "rollingCountFallbackFailure": 0, + "rollingCountFallbackRejection": 0, + "rollingCountFallbackSuccess": 0, + "rollingCountResponsesFromCache": 69, + "rollingCountSemaphoreRejected": 0, + "rollingCountShortCircuited": 0, + "rollingCountSuccess": 121, + "rollingCountThreadPoolRejected": 0, + "rollingCountTimeout": 0, + "currentConcurrentExecutionCount": 0, + "latencyExecute_mean": 13, + "latencyExecute": { + "0": 3, + "25": 6, + "50": 8, + "75": 14, + "90": 26, + "95": 37, + "99": 75, + "99.5": 92, + "100": 252 + }, + "latencyTotal_mean": 15, + "latencyTotal": { + "0": 3, + "25": 7, + "50": 10, + "75": 18, + "90": 32, + "95": 43, + "99": 88, + "99.5": 160, + "100": 253 + }, + "propertyValue_circuitBreakerRequestVolumeThreshold": 20, + "propertyValue_circuitBreakerSleepWindowInMilliseconds": 5000, + "propertyValue_circuitBreakerErrorThresholdPercentage": 50, + "propertyValue_circuitBreakerForceOpen": false, + "propertyValue_circuitBreakerForceClosed": false, + "propertyValue_circuitBreakerEnabled": true, + "propertyValue_executionIsolationStrategy": "THREAD", + "propertyValue_executionIsolationThreadTimeoutInMilliseconds": 800, + "propertyValue_executionIsolationThreadInterruptOnTimeout": true, + "propertyValue_executionIsolationThreadPoolKeyOverride": null, + "propertyValue_executionIsolationSemaphoreMaxConcurrentRequests": 20, + "propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests": 10, + "propertyValue_metricsRollingStatisticalWindowInMilliseconds": 10000, + "propertyValue_requestCacheEnabled": true, + "propertyValue_requestLogEnabled": true, + "reportingHosts": 1 +} +``` + +HystrixThreadPool instances will emit data such as this: + +```json +data: +{ + "currentPoolSize": 30, + "rollingMaxActiveThreads": 13, + "currentActiveCount": 0, + "currentCompletedTaskCount": 4459519, + "propertyValue_queueSizeRejectionThreshold": 30, + "type": "HystrixThreadPool", + "reportingHosts": 3, + "propertyValue_metricsRollingStatisticalWindowInMilliseconds": 30000, + "name": "ABClient", + "currentLargestPoolSize": 30, + "currentCorePoolSize": 30, + "currentQueueSize": 0, + "currentTaskCount": 4459519, + "rollingCountThreadsExecuted": 919, + "currentMaximumPoolSize": 30 +} +``` + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-metrics-event-stream%22). + +Example for Maven ([lookup latest version](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-metrics-event-stream%22)): + +```xml + + com.netflix.hystrix + hystrix-metrics-event-stream + 1.4.10 + +``` +and for Ivy: + +```xml + +``` + +# Installation + +1) Include hystrix-metrics-event-stream-*.jar in your classpath (such as /WEB-INF/lib) +2) Add the following to your application web.xml: + +```xml + + + HystrixMetricsStreamServlet + HystrixMetricsStreamServlet + com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet + + + + HystrixMetricsStreamServlet + /hystrix.stream + +``` + +# Test + +To test your installation you can use curl like this: + +``` +$ curl http://hostname:port/appname/hystrix.stream + +data: {"rollingCountFailure":0,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"rollingCountTimeout":0,"rollingCountExceptionsThrown":0,"rollingCountFallbackSuccess":0,"errorCount":0,"type":"HystrixCommand","propertyValue_circuitBreakerEnabled":true,"reportingHosts":1,"latencyTotal":{"0":0,"95":0,"99.5":0,"90":0,"25":0,"99":0,"75":0,"100":0,"50":0},"currentConcurrentExecutionCount":0,"rollingCountSemaphoreRejected":0,"rollingCountFallbackRejection":0,"rollingCountShortCircuited":0,"rollingCountResponsesFromCache":0,"propertyValue_circuitBreakerForceClosed":false,"name":"IdentityCookieAuthSwitchProfile","propertyValue_executionIsolationThreadPoolKeyOverride":"null","rollingCountSuccess":0,"propertyValue_requestLogEnabled":true,"requestCount":0,"rollingCountCollapsedRequests":0,"errorPercentage":0,"propertyValue_circuitBreakerSleepWindowInMilliseconds":5000,"latencyTotal_mean":0,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_circuitBreakerRequestVolumeThreshold":20,"propertyValue_circuitBreakerErrorThresholdPercentage":50,"propertyValue_executionIsolationStrategy":"THREAD","rollingCountFallbackFailure":0,"isCircuitBreakerOpen":false,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":20,"propertyValue_executionIsolationThreadTimeoutInMilliseconds":1000,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":10,"latencyExecute":{"0":0,"95":0,"99.5":0,"90":0,"25":0,"99":0,"75":0,"100":0,"50":0},"group":"IDENTITY","latencyExecute_mean":0,"propertyValue_requestCacheEnabled":true,"rollingCountThreadPoolRejected":0} + +data: {"rollingCountFailure":0,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"rollingCountTimeout":0,"rollingCountExceptionsThrown":0,"rollingCountFallbackSuccess":0,"errorCount":0,"type":"HystrixCommand","propertyValue_circuitBreakerEnabled":true,"reportingHosts":3,"latencyTotal":{"0":1,"95":1,"99.5":1,"90":1,"25":1,"99":1,"75":1,"100":1,"50":1},"currentConcurrentExecutionCount":0,"rollingCountSemaphoreRejected":0,"rollingCountFallbackRejection":0,"rollingCountShortCircuited":0,"rollingCountResponsesFromCache":0,"propertyValue_circuitBreakerForceClosed":false,"name":"CryptexDecrypt","propertyValue_executionIsolationThreadPoolKeyOverride":"null","rollingCountSuccess":1,"propertyValue_requestLogEnabled":true,"requestCount":1,"rollingCountCollapsedRequests":0,"errorPercentage":0,"propertyValue_circuitBreakerSleepWindowInMilliseconds":15000,"latencyTotal_mean":1,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_circuitBreakerRequestVolumeThreshold":60,"propertyValue_circuitBreakerErrorThresholdPercentage":150,"propertyValue_executionIsolationStrategy":"THREAD","rollingCountFallbackFailure":0,"isCircuitBreakerOpen":false,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":60,"propertyValue_executionIsolationThreadTimeoutInMilliseconds":3000,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":30000,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":30,"latencyExecute":{"0":0,"95":0,"99.5":0,"90":0,"25":0,"99":0,"75":0,"100":0,"50":0},"group":"CRYPTEX","latencyExecute_mean":0,"propertyValue_requestCacheEnabled":true,"rollingCountThreadPoolRejected":0} +``` + +# Clojure Version + +A Clojure version of this module can be found at https://github.com/josephwilk/hystrix-event-stream-clj diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/build.gradle new file mode 100644 index 0000000..6d7ef83 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/build.gradle @@ -0,0 +1,7 @@ +dependencies { + compileApi project(':hystrix-core') + compile project(':hystrix-serialization') + provided 'javax.servlet:servlet-api:2.5' + testCompile 'junit:junit-dep:4.10' + testCompile 'org.mockito:mockito-all:1.9.5' +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsPoller.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsPoller.java new file mode 100644 index 0000000..e6510de --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsPoller.java @@ -0,0 +1,527 @@ +/** + * Copyright 2012 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.contrib.metrics.eventstream; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.util.PlatformSpecific; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Polls Hystrix metrics and output JSON strings for each metric to a MetricsPollerListener. + *

+ * Polling can be stopped/started. Use shutdown() to permanently shutdown the poller. + * + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-metrics-event-stream, + * the code below may reference a HystrixEventType that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + * + * @deprecated Prefer {@link com.netflix.hystrix.metric.consumer.HystrixDashboardStream} + */ +@Deprecated //since 1.5.4 +public class HystrixMetricsPoller { + + static final Logger logger = LoggerFactory.getLogger(HystrixMetricsPoller.class); + private final ScheduledExecutorService executor; + private final int delay; + private final AtomicBoolean running = new AtomicBoolean(false); + private volatile ScheduledFuture scheduledTask = null; + private final MetricsAsJsonPollerListener listener; + + /** + * Allocate resources to begin polling. + *

+ * Use start to begin polling. + *

+ * Use shutdown to cleanup resources and stop polling. + *

+ * Use pause to temporarily stop polling that can be restarted again with start. + * + * @param listener for callbacks + * @param delay + */ + public HystrixMetricsPoller(MetricsAsJsonPollerListener listener, int delay) { + this.listener = listener; + + ThreadFactory threadFactory = null; + if (!PlatformSpecific.isAppEngineStandardEnvironment()) { + threadFactory = new MetricsPollerThreadFactory(); + } else { + threadFactory = PlatformSpecific.getAppEngineThreadFactory(); + } + + executor = new ScheduledThreadPoolExecutor(1, threadFactory); + this.delay = delay; + } + + /** + * Start polling. + */ + public synchronized void start() { + // use compareAndSet to make sure it starts only once and when not running + if (running.compareAndSet(false, true)) { + logger.debug("Starting HystrixMetricsPoller"); + try { + scheduledTask = executor.scheduleWithFixedDelay(new MetricsPoller(listener), 0, delay, TimeUnit.MILLISECONDS); + } catch (Throwable ex) { + logger.error("Exception while creating the MetricsPoller task"); + ex.printStackTrace(); + running.set(false); + } + } + } + + /** + * Pause (stop) polling. Polling can be started again with start as long as shutdown is not called. + */ + public synchronized void pause() { + // use compareAndSet to make sure it stops only once and when running + if (running.compareAndSet(true, false)) { + logger.debug("Stopping the HystrixMetricsPoller"); + scheduledTask.cancel(true); + } else { + logger.debug("Attempted to pause a stopped poller"); + } + } + + /** + * Stops polling and shuts down the ExecutorService. + *

+ * This instance can no longer be used after calling shutdown. + */ + public synchronized void shutdown() { + pause(); + executor.shutdown(); + } + + public boolean isRunning() { + return running.get(); + } + + /** + * Used to protect against leaking ExecutorServices and threads if this class is abandoned for GC without shutting down. + */ + @SuppressWarnings("unused") + private final Object finalizerGuardian = new Object() { + protected void finalize() throws Throwable { + if (!executor.isShutdown()) { + logger.warn("{} was not shutdown. Caught in Finalize Guardian and shutting down.", HystrixMetricsPoller.class.getSimpleName()); + try { + shutdown(); + } catch (Exception e) { + logger.error("Failed to shutdown {}", HystrixMetricsPoller.class.getSimpleName(), e); + } + } + }; + }; + + public static interface MetricsAsJsonPollerListener { + public void handleJsonMetric(String json); + } + + private class MetricsPoller implements Runnable { + + private final MetricsAsJsonPollerListener listener; + private final JsonFactory jsonFactory = new JsonFactory(); + + public MetricsPoller(MetricsAsJsonPollerListener listener) { + this.listener = listener; + } + + @Override + public void run() { + try { + for (HystrixCommandMetrics commandMetrics : HystrixCommandMetrics.getInstances()) { + String jsonString = getCommandJson(commandMetrics); + listener.handleJsonMetric(jsonString); + } + + for (HystrixThreadPoolMetrics threadPoolMetrics : HystrixThreadPoolMetrics.getInstances()) { + if (hasExecutedCommandsOnThread(threadPoolMetrics)) { + String jsonString = getThreadPoolJson(threadPoolMetrics); + listener.handleJsonMetric(jsonString); + } + } + + for (HystrixCollapserMetrics collapserMetrics : HystrixCollapserMetrics.getInstances()) { + String jsonString = getCollapserJson(collapserMetrics); + listener.handleJsonMetric(jsonString); + } + + } catch (Exception e) { + logger.warn("Failed to output metrics as JSON", e); + // shutdown + pause(); + return; + } + } + + private void safelyWriteNumberField(JsonGenerator json, String name, Func0 metricGenerator) throws IOException { + try { + json.writeNumberField(name, metricGenerator.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing Hystrix metrics stream, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + json.writeNumberField(name, 0L); + } + } + + private String getCommandJson(final HystrixCommandMetrics commandMetrics) throws IOException { + HystrixCommandKey key = commandMetrics.getCommandKey(); + HystrixCircuitBreaker circuitBreaker = HystrixCircuitBreaker.Factory.getInstance(key); + + StringWriter jsonString = new StringWriter(); + JsonGenerator json = jsonFactory.createGenerator(jsonString); + + json.writeStartObject(); + json.writeStringField("type", "HystrixCommand"); + json.writeStringField("name", key.name()); + json.writeStringField("group", commandMetrics.getCommandGroup().name()); + json.writeNumberField("currentTime", System.currentTimeMillis()); + + // circuit breaker + if (circuitBreaker == null) { + // circuit breaker is disabled and thus never open + json.writeBooleanField("isCircuitBreakerOpen", false); + } else { + json.writeBooleanField("isCircuitBreakerOpen", circuitBreaker.isOpen()); + } + HealthCounts healthCounts = commandMetrics.getHealthCounts(); + json.writeNumberField("errorPercentage", healthCounts.getErrorPercentage()); + json.writeNumberField("errorCount", healthCounts.getErrorCount()); + json.writeNumberField("requestCount", healthCounts.getTotalRequests()); + + // rolling counters + safelyWriteNumberField(json, "rollingCountBadRequests", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.BAD_REQUEST); + } + }); + safelyWriteNumberField(json, "rollingCountCollapsedRequests", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.COLLAPSED); + } + }); + safelyWriteNumberField(json, "rollingCountEmit", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.EMIT); + } + }); + safelyWriteNumberField(json, "rollingCountExceptionsThrown", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.EXCEPTION_THROWN); + } + }); + safelyWriteNumberField(json, "rollingCountFailure", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FAILURE); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackEmit", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_EMIT); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackFailure", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_FAILURE); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackMissing", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_MISSING); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackRejection", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_REJECTION); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackSuccess", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_SUCCESS); + } + }); + safelyWriteNumberField(json, "rollingCountResponsesFromCache", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.RESPONSE_FROM_CACHE); + } + }); + safelyWriteNumberField(json, "rollingCountSemaphoreRejected", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.SEMAPHORE_REJECTED); + } + }); + safelyWriteNumberField(json, "rollingCountShortCircuited", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.SHORT_CIRCUITED); + } + }); + safelyWriteNumberField(json, "rollingCountSuccess", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.SUCCESS); + } + }); + safelyWriteNumberField(json, "rollingCountThreadPoolRejected", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.THREAD_POOL_REJECTED); + } + }); + safelyWriteNumberField(json, "rollingCountTimeout", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.TIMEOUT); + } + }); + + json.writeNumberField("currentConcurrentExecutionCount", commandMetrics.getCurrentConcurrentExecutionCount()); + json.writeNumberField("rollingMaxConcurrentExecutionCount", commandMetrics.getRollingMaxConcurrentExecutions()); + + // latency percentiles + json.writeNumberField("latencyExecute_mean", commandMetrics.getExecutionTimeMean()); + json.writeObjectFieldStart("latencyExecute"); + json.writeNumberField("0", commandMetrics.getExecutionTimePercentile(0)); + json.writeNumberField("25", commandMetrics.getExecutionTimePercentile(25)); + json.writeNumberField("50", commandMetrics.getExecutionTimePercentile(50)); + json.writeNumberField("75", commandMetrics.getExecutionTimePercentile(75)); + json.writeNumberField("90", commandMetrics.getExecutionTimePercentile(90)); + json.writeNumberField("95", commandMetrics.getExecutionTimePercentile(95)); + json.writeNumberField("99", commandMetrics.getExecutionTimePercentile(99)); + json.writeNumberField("99.5", commandMetrics.getExecutionTimePercentile(99.5)); + json.writeNumberField("100", commandMetrics.getExecutionTimePercentile(100)); + json.writeEndObject(); + // + json.writeNumberField("latencyTotal_mean", commandMetrics.getTotalTimeMean()); + json.writeObjectFieldStart("latencyTotal"); + json.writeNumberField("0", commandMetrics.getTotalTimePercentile(0)); + json.writeNumberField("25", commandMetrics.getTotalTimePercentile(25)); + json.writeNumberField("50", commandMetrics.getTotalTimePercentile(50)); + json.writeNumberField("75", commandMetrics.getTotalTimePercentile(75)); + json.writeNumberField("90", commandMetrics.getTotalTimePercentile(90)); + json.writeNumberField("95", commandMetrics.getTotalTimePercentile(95)); + json.writeNumberField("99", commandMetrics.getTotalTimePercentile(99)); + json.writeNumberField("99.5", commandMetrics.getTotalTimePercentile(99.5)); + json.writeNumberField("100", commandMetrics.getTotalTimePercentile(100)); + json.writeEndObject(); + + // property values for reporting what is actually seen by the command rather than what was set somewhere + HystrixCommandProperties commandProperties = commandMetrics.getProperties(); + + json.writeNumberField("propertyValue_circuitBreakerRequestVolumeThreshold", commandProperties.circuitBreakerRequestVolumeThreshold().get()); + json.writeNumberField("propertyValue_circuitBreakerSleepWindowInMilliseconds", commandProperties.circuitBreakerSleepWindowInMilliseconds().get()); + json.writeNumberField("propertyValue_circuitBreakerErrorThresholdPercentage", commandProperties.circuitBreakerErrorThresholdPercentage().get()); + json.writeBooleanField("propertyValue_circuitBreakerForceOpen", commandProperties.circuitBreakerForceOpen().get()); + json.writeBooleanField("propertyValue_circuitBreakerForceClosed", commandProperties.circuitBreakerForceClosed().get()); + json.writeBooleanField("propertyValue_circuitBreakerEnabled", commandProperties.circuitBreakerEnabled().get()); + + json.writeStringField("propertyValue_executionIsolationStrategy", commandProperties.executionIsolationStrategy().get().name()); + json.writeNumberField("propertyValue_executionIsolationThreadTimeoutInMilliseconds", commandProperties.executionTimeoutInMilliseconds().get()); + json.writeNumberField("propertyValue_executionTimeoutInMilliseconds", commandProperties.executionTimeoutInMilliseconds().get()); + json.writeBooleanField("propertyValue_executionIsolationThreadInterruptOnTimeout", commandProperties.executionIsolationThreadInterruptOnTimeout().get()); + json.writeStringField("propertyValue_executionIsolationThreadPoolKeyOverride", commandProperties.executionIsolationThreadPoolKeyOverride().get()); + json.writeNumberField("propertyValue_executionIsolationSemaphoreMaxConcurrentRequests", commandProperties.executionIsolationSemaphoreMaxConcurrentRequests().get()); + json.writeNumberField("propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests", commandProperties.fallbackIsolationSemaphoreMaxConcurrentRequests().get()); + + /* + * The following are commented out as these rarely change and are verbose for streaming for something people don't change. + * We could perhaps allow a property or request argument to include these. + */ + + // json.put("propertyValue_metricsRollingPercentileEnabled", commandProperties.metricsRollingPercentileEnabled().get()); + // json.put("propertyValue_metricsRollingPercentileBucketSize", commandProperties.metricsRollingPercentileBucketSize().get()); + // json.put("propertyValue_metricsRollingPercentileWindow", commandProperties.metricsRollingPercentileWindowInMilliseconds().get()); + // json.put("propertyValue_metricsRollingPercentileWindowBuckets", commandProperties.metricsRollingPercentileWindowBuckets().get()); + // json.put("propertyValue_metricsRollingStatisticalWindowBuckets", commandProperties.metricsRollingStatisticalWindowBuckets().get()); + json.writeNumberField("propertyValue_metricsRollingStatisticalWindowInMilliseconds", commandProperties.metricsRollingStatisticalWindowInMilliseconds().get()); + + json.writeBooleanField("propertyValue_requestCacheEnabled", commandProperties.requestCacheEnabled().get()); + json.writeBooleanField("propertyValue_requestLogEnabled", commandProperties.requestLogEnabled().get()); + + json.writeNumberField("reportingHosts", 1); // this will get summed across all instances in a cluster + json.writeStringField("threadPool", commandMetrics.getThreadPoolKey().name()); + + json.writeEndObject(); + json.close(); + + return jsonString.getBuffer().toString(); + } + + private boolean hasExecutedCommandsOnThread(HystrixThreadPoolMetrics threadPoolMetrics) { + return threadPoolMetrics.getCurrentCompletedTaskCount().intValue() > 0; + } + + private String getThreadPoolJson(final HystrixThreadPoolMetrics threadPoolMetrics) throws IOException { + HystrixThreadPoolKey key = threadPoolMetrics.getThreadPoolKey(); + StringWriter jsonString = new StringWriter(); + JsonGenerator json = jsonFactory.createJsonGenerator(jsonString); + json.writeStartObject(); + + json.writeStringField("type", "HystrixThreadPool"); + json.writeStringField("name", key.name()); + json.writeNumberField("currentTime", System.currentTimeMillis()); + + json.writeNumberField("currentActiveCount", threadPoolMetrics.getCurrentActiveCount().intValue()); + json.writeNumberField("currentCompletedTaskCount", threadPoolMetrics.getCurrentCompletedTaskCount().longValue()); + json.writeNumberField("currentCorePoolSize", threadPoolMetrics.getCurrentCorePoolSize().intValue()); + json.writeNumberField("currentLargestPoolSize", threadPoolMetrics.getCurrentLargestPoolSize().intValue()); + json.writeNumberField("currentMaximumPoolSize", threadPoolMetrics.getCurrentMaximumPoolSize().intValue()); + json.writeNumberField("currentPoolSize", threadPoolMetrics.getCurrentPoolSize().intValue()); + json.writeNumberField("currentQueueSize", threadPoolMetrics.getCurrentQueueSize().intValue()); + json.writeNumberField("currentTaskCount", threadPoolMetrics.getCurrentTaskCount().longValue()); + safelyWriteNumberField(json, "rollingCountThreadsExecuted", new Func0() { + @Override + public Long call() { + return threadPoolMetrics.getRollingCount(HystrixEventType.ThreadPool.EXECUTED); + } + }); + json.writeNumberField("rollingMaxActiveThreads", threadPoolMetrics.getRollingMaxActiveThreads()); + safelyWriteNumberField(json, "rollingCountCommandRejections", new Func0() { + @Override + public Long call() { + return threadPoolMetrics.getRollingCount(HystrixEventType.ThreadPool.REJECTED); + } + }); + + json.writeNumberField("propertyValue_queueSizeRejectionThreshold", threadPoolMetrics.getProperties().queueSizeRejectionThreshold().get()); + json.writeNumberField("propertyValue_metricsRollingStatisticalWindowInMilliseconds", threadPoolMetrics.getProperties().metricsRollingStatisticalWindowInMilliseconds().get()); + + json.writeNumberField("reportingHosts", 1); // this will get summed across all instances in a cluster + + json.writeEndObject(); + json.close(); + + return jsonString.getBuffer().toString(); + } + + private String getCollapserJson(final HystrixCollapserMetrics collapserMetrics) throws IOException { + HystrixCollapserKey key = collapserMetrics.getCollapserKey(); + StringWriter jsonString = new StringWriter(); + JsonGenerator json = jsonFactory.createJsonGenerator(jsonString); + json.writeStartObject(); + + json.writeStringField("type", "HystrixCollapser"); + json.writeStringField("name", key.name()); + json.writeNumberField("currentTime", System.currentTimeMillis()); + + safelyWriteNumberField(json, "rollingCountRequestsBatched", new Func0() { + @Override + public Long call() { + return collapserMetrics.getRollingCount(HystrixEventType.Collapser.ADDED_TO_BATCH); + } + }); + safelyWriteNumberField(json, "rollingCountBatches", new Func0() { + @Override + public Long call() { + return collapserMetrics.getRollingCount(HystrixEventType.Collapser.BATCH_EXECUTED); + } + }); + safelyWriteNumberField(json, "rollingCountResponsesFromCache", new Func0() { + @Override + public Long call() { + return collapserMetrics.getRollingCount(HystrixEventType.Collapser.RESPONSE_FROM_CACHE); + } + }); + + // batch size percentiles + json.writeNumberField("batchSize_mean", collapserMetrics.getBatchSizeMean()); + json.writeObjectFieldStart("batchSize"); + json.writeNumberField("25", collapserMetrics.getBatchSizePercentile(25)); + json.writeNumberField("50", collapserMetrics.getBatchSizePercentile(50)); + json.writeNumberField("75", collapserMetrics.getBatchSizePercentile(75)); + json.writeNumberField("90", collapserMetrics.getBatchSizePercentile(90)); + json.writeNumberField("95", collapserMetrics.getBatchSizePercentile(95)); + json.writeNumberField("99", collapserMetrics.getBatchSizePercentile(99)); + json.writeNumberField("99.5", collapserMetrics.getBatchSizePercentile(99.5)); + json.writeNumberField("100", collapserMetrics.getBatchSizePercentile(100)); + json.writeEndObject(); + + // shard size percentiles (commented-out for now) + //json.writeNumberField("shardSize_mean", collapserMetrics.getShardSizeMean()); + //json.writeObjectFieldStart("shardSize"); + //json.writeNumberField("25", collapserMetrics.getShardSizePercentile(25)); + //json.writeNumberField("50", collapserMetrics.getShardSizePercentile(50)); + //json.writeNumberField("75", collapserMetrics.getShardSizePercentile(75)); + //json.writeNumberField("90", collapserMetrics.getShardSizePercentile(90)); + //json.writeNumberField("95", collapserMetrics.getShardSizePercentile(95)); + //json.writeNumberField("99", collapserMetrics.getShardSizePercentile(99)); + //json.writeNumberField("99.5", collapserMetrics.getShardSizePercentile(99.5)); + //json.writeNumberField("100", collapserMetrics.getShardSizePercentile(100)); + //json.writeEndObject(); + + //json.writeNumberField("propertyValue_metricsRollingStatisticalWindowInMilliseconds", collapserMetrics.getProperties().metricsRollingStatisticalWindowInMilliseconds().get()); + json.writeBooleanField("propertyValue_requestCacheEnabled", collapserMetrics.getProperties().requestCacheEnabled().get()); + json.writeNumberField("propertyValue_maxRequestsInBatch", collapserMetrics.getProperties().maxRequestsInBatch().get()); + json.writeNumberField("propertyValue_timerDelayInMilliseconds", collapserMetrics.getProperties().timerDelayInMilliseconds().get()); + + json.writeNumberField("reportingHosts", 1); // this will get summed across all instances in a cluster + + json.writeEndObject(); + json.close(); + + return jsonString.getBuffer().toString(); + } + } + + private static class MetricsPollerThreadFactory implements ThreadFactory { + private static final String MetricsThreadName = "HystrixMetricPoller"; + + private final ThreadFactory defaultFactory = Executors.defaultThreadFactory(); + + public Thread newThread(Runnable r) { + Thread thread = defaultFactory.newThread(r); + thread.setName(MetricsThreadName); + thread.setDaemon(true); + return thread; + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsStreamServlet.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsStreamServlet.java new file mode 100644 index 0000000..fb53c66 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsStreamServlet.java @@ -0,0 +1,90 @@ +/** + * Copyright 2012 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.contrib.metrics.eventstream; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.contrib.sample.stream.HystrixSampleSseServlet; +import com.netflix.hystrix.metric.consumer.HystrixDashboardStream; +import com.netflix.hystrix.serial.SerialHystrixDashboardData; +import rx.Observable; +import rx.functions.Func1; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Streams Hystrix metrics in text/event-stream format. + *

+ * Install by: + *

+ * 1) Including hystrix-metrics-event-stream-*.jar in your classpath. + *

+ * 2) Adding the following to web.xml: + *

{@code
+ * 
+ *  
+ *  HystrixMetricsStreamServlet
+ *  HystrixMetricsStreamServlet
+ *  com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet
+ * 
+ * 
+ *  HystrixMetricsStreamServlet
+ *  /hystrix.stream
+ * 
+ * } 
+ */ +public class HystrixMetricsStreamServlet extends HystrixSampleSseServlet { + + private static final long serialVersionUID = -7548505095303313237L; + + /* used to track number of connections and throttle */ + private static AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = + DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public HystrixMetricsStreamServlet() { + this(HystrixDashboardStream.getInstance().observe(), DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS); + } + + /* package-private */ HystrixMetricsStreamServlet(Observable sampleStream, int pausePollerThreadDelayInMs) { + super(sampleStream.concatMap(new Func1>() { + @Override + public Observable call(HystrixDashboardStream.DashboardData dashboardData) { + return Observable.from(SerialHystrixDashboardData.toMultipleJsonStrings(dashboardData)); + } + }), pausePollerThreadDelayInMs); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + + @Override + protected int getNumberCurrentConnections() { + return concurrentConnections.get(); + } + + @Override + protected int incrementAndGetCurrentConcurrentConnections() { + return concurrentConnections.incrementAndGet(); + } + + @Override + protected void decrementCurrentConcurrentConnections() { + concurrentConnections.decrementAndGet(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/requests/stream/HystrixRequestEventsJsonStream.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/requests/stream/HystrixRequestEventsJsonStream.java new file mode 100644 index 0000000..9598d11 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/requests/stream/HystrixRequestEventsJsonStream.java @@ -0,0 +1,116 @@ +/** + * Copyright 2016 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.contrib.requests.stream; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.netflix.hystrix.ExecutionResult; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.HystrixRequestEvents; +import com.netflix.hystrix.metric.HystrixRequestEventsStream; +import rx.Observable; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Stream that converts HystrixRequestEvents into JSON. This isn't needed anymore, as it more straightforward + * to consider serialization completely separately from the domain object stream + * + * @deprecated Instead, prefer mapping your preferred serialization on top of {@link HystrixRequestEventsStream#observe()}. + */ +@Deprecated //since 1.5.4 +public class HystrixRequestEventsJsonStream { + private static final JsonFactory jsonFactory = new JsonFactory(); + + public Observable getStream() { + return HystrixRequestEventsStream.getInstance() + .observe(); + } + + public static String convertRequestsToJson(Collection requests) throws IOException { + StringWriter jsonString = new StringWriter(); + JsonGenerator json = jsonFactory.createGenerator(jsonString); + + json.writeStartArray(); + for (HystrixRequestEvents request : requests) { + writeRequestAsJson(json, request); + } + json.writeEndArray(); + json.close(); + return jsonString.getBuffer().toString(); + } + + public static String convertRequestToJson(HystrixRequestEvents request) throws IOException { + StringWriter jsonString = new StringWriter(); + JsonGenerator json = jsonFactory.createGenerator(jsonString); + writeRequestAsJson(json, request); + json.close(); + return jsonString.getBuffer().toString(); + } + + + private static void writeRequestAsJson(JsonGenerator json, HystrixRequestEvents request) throws IOException { + json.writeStartArray(); + + for (Map.Entry> entry: request.getExecutionsMappedToLatencies().entrySet()) { + convertExecutionToJson(json, entry.getKey(), entry.getValue()); + } + + json.writeEndArray(); + } + + private static void convertExecutionToJson(JsonGenerator json, HystrixRequestEvents.ExecutionSignature executionSignature, List latencies) throws IOException { + json.writeStartObject(); + json.writeStringField("name", executionSignature.getCommandName()); + json.writeArrayFieldStart("events"); + ExecutionResult.EventCounts eventCounts = executionSignature.getEventCounts(); + for (HystrixEventType eventType: HystrixEventType.values()) { + if (eventType != HystrixEventType.COLLAPSED) { + if (eventCounts.contains(eventType)) { + int eventCount = eventCounts.getCount(eventType); + if (eventCount > 1) { + json.writeStartObject(); + json.writeStringField("name", eventType.name()); + json.writeNumberField("count", eventCount); + json.writeEndObject(); + } else { + json.writeString(eventType.name()); + } + } + } + } + json.writeEndArray(); + json.writeArrayFieldStart("latencies"); + for (int latency: latencies) { + json.writeNumber(latency); + } + json.writeEndArray(); + if (executionSignature.getCachedCount() > 0) { + json.writeNumberField("cached", executionSignature.getCachedCount()); + } + if (executionSignature.getEventCounts().contains(HystrixEventType.COLLAPSED)) { + json.writeObjectFieldStart("collapsed"); + json.writeStringField("name", executionSignature.getCollapserKey().name()); + json.writeNumberField("count", executionSignature.getCollapserBatchSize()); + json.writeEndObject(); + } + json.writeEndObject(); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/requests/stream/HystrixRequestEventsSseServlet.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/requests/stream/HystrixRequestEventsSseServlet.java new file mode 100644 index 0000000..b110965 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/requests/stream/HystrixRequestEventsSseServlet.java @@ -0,0 +1,74 @@ +/** + * Copyright 2016 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.contrib.requests.stream; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.contrib.sample.stream.HystrixSampleSseServlet; +import com.netflix.hystrix.metric.HystrixRequestEvents; +import com.netflix.hystrix.metric.HystrixRequestEventsStream; +import com.netflix.hystrix.serial.SerialHystrixRequestEvents; +import rx.Observable; +import rx.functions.Func1; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Servlet that writes SSE JSON every time a request is made + */ +public class HystrixRequestEventsSseServlet extends HystrixSampleSseServlet { + + private static final long serialVersionUID = 6389353893099737870L; + + /* used to track number of connections and throttle */ + private static AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = + DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public HystrixRequestEventsSseServlet() { + this(HystrixRequestEventsStream.getInstance().observe(), DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS); + } + + /* package-private */ HystrixRequestEventsSseServlet(Observable sampleStream, int pausePollerThreadDelayInMs) { + super(sampleStream.map(new Func1() { + @Override + public String call(HystrixRequestEvents requestEvents) { + return SerialHystrixRequestEvents.toJsonString(requestEvents); + } + }), pausePollerThreadDelayInMs); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + + @Override + protected int getNumberCurrentConnections() { + return concurrentConnections.get(); + } + + @Override + protected int incrementAndGetCurrentConcurrentConnections() { + return concurrentConnections.incrementAndGet(); + } + + @Override + protected void decrementCurrentConcurrentConnections() { + concurrentConnections.decrementAndGet(); + } +} + + diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigSseServlet.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigSseServlet.java new file mode 100644 index 0000000..d176850 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigSseServlet.java @@ -0,0 +1,90 @@ +/** + * Copyright 2016 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.contrib.sample.stream; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.config.HystrixConfiguration; +import com.netflix.hystrix.config.HystrixConfigurationStream; +import com.netflix.hystrix.serial.SerialHystrixConfiguration; +import rx.Observable; +import rx.functions.Func1; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Streams Hystrix config in text/event-stream format. + *

+ * Install by: + *

+ * 1) Including hystrix-metrics-event-stream-*.jar in your classpath. + *

+ * 2) Adding the following to web.xml: + *

{@code
+ * 
+ *  
+ *  HystrixConfigSseServlet
+ *  HystrixConfigSseServlet
+ *  com.netflix.hystrix.contrib.sample.stream.HystrixConfigSseServlet
+ * 
+ * 
+ *  HystrixConfigSseServlet
+ *  /hystrix/config.stream
+ * 
+ * } 
+ */ +public class HystrixConfigSseServlet extends HystrixSampleSseServlet { + + private static final long serialVersionUID = -3599771169762858235L; + + /* used to track number of connections and throttle */ + private static AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public HystrixConfigSseServlet() { + this(HystrixConfigurationStream.getInstance().observe(), DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS); + } + + /* package-private */ HystrixConfigSseServlet(Observable sampleStream, int pausePollerThreadDelayInMs) { + super(sampleStream.map(new Func1() { + @Override + public String call(HystrixConfiguration hystrixConfiguration) { + return SerialHystrixConfiguration.toJsonString(hystrixConfiguration); + } + }), pausePollerThreadDelayInMs); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + + @Override + protected int getNumberCurrentConnections() { + return concurrentConnections.get(); + } + + @Override + protected int incrementAndGetCurrentConcurrentConnections() { + return concurrentConnections.incrementAndGet(); + } + + @Override + protected void decrementCurrentConcurrentConnections() { + concurrentConnections.decrementAndGet(); + } +} + diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigurationJsonStream.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigurationJsonStream.java new file mode 100644 index 0000000..a6ee22d --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigurationJsonStream.java @@ -0,0 +1,204 @@ +/** + * Copyright 2016 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.contrib.sample.stream; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.config.HystrixCollapserConfiguration; +import com.netflix.hystrix.config.HystrixCommandConfiguration; +import com.netflix.hystrix.config.HystrixConfiguration; +import com.netflix.hystrix.config.HystrixConfigurationStream; +import com.netflix.hystrix.config.HystrixThreadPoolConfiguration; +import com.netflix.hystrix.metric.HystrixRequestEventsStream; +import rx.Observable; +import rx.functions.Func1; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Map; + +/** + * Links HystrixConfigurationStream and JSON encoding. This may be consumed in a variety of ways: + * -such as- + *

    + *
  • {@link HystrixConfigSseServlet} for mapping a specific URL to this data as an SSE stream + *
  • Consumer of your choice that wants control over where to embed this stream + *
+ * + * @deprecated Instead, prefer mapping your preferred serialization on top of {@link HystrixConfigurationStream#observe()}. + */ +@Deprecated //since 1.5.4 +public class HystrixConfigurationJsonStream { + + private static final JsonFactory jsonFactory = new JsonFactory(); + private final Func1> streamGenerator; + + @Deprecated //since 1.5.4 + public HystrixConfigurationJsonStream() { + this.streamGenerator = new Func1>() { + @Override + public Observable call(Integer delay) { + return HystrixConfigurationStream.getInstance().observe(); + } + }; + } + + @Deprecated //since 1.5.4 + public HystrixConfigurationJsonStream(Func1> streamGenerator) { + this.streamGenerator = streamGenerator; + } + + private static final Func1 convertToJson = new Func1() { + @Override + public String call(HystrixConfiguration hystrixConfiguration) { + try { + return convertToString(hystrixConfiguration); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + }; + + private static void writeCommandConfigJson(JsonGenerator json, HystrixCommandKey key, HystrixCommandConfiguration commandConfig) throws IOException { + json.writeObjectFieldStart(key.name()); + json.writeStringField("threadPoolKey", commandConfig.getThreadPoolKey().name()); + json.writeStringField("groupKey", commandConfig.getGroupKey().name()); + json.writeObjectFieldStart("execution"); + HystrixCommandConfiguration.HystrixCommandExecutionConfig executionConfig = commandConfig.getExecutionConfig(); + json.writeStringField("isolationStrategy", executionConfig.getIsolationStrategy().name()); + json.writeStringField("threadPoolKeyOverride", executionConfig.getThreadPoolKeyOverride()); + json.writeBooleanField("requestCacheEnabled", executionConfig.isRequestCacheEnabled()); + json.writeBooleanField("requestLogEnabled", executionConfig.isRequestLogEnabled()); + json.writeBooleanField("timeoutEnabled", executionConfig.isTimeoutEnabled()); + json.writeBooleanField("fallbackEnabled", executionConfig.isFallbackEnabled()); + json.writeNumberField("timeoutInMilliseconds", executionConfig.getTimeoutInMilliseconds()); + json.writeNumberField("semaphoreSize", executionConfig.getSemaphoreMaxConcurrentRequests()); + json.writeNumberField("fallbackSemaphoreSize", executionConfig.getFallbackMaxConcurrentRequest()); + json.writeBooleanField("threadInterruptOnTimeout", executionConfig.isThreadInterruptOnTimeout()); + json.writeEndObject(); + json.writeObjectFieldStart("metrics"); + HystrixCommandConfiguration.HystrixCommandMetricsConfig metricsConfig = commandConfig.getMetricsConfig(); + json.writeNumberField("healthBucketSizeInMs", metricsConfig.getHealthIntervalInMilliseconds()); + json.writeNumberField("percentileBucketSizeInMilliseconds", metricsConfig.getRollingPercentileBucketSizeInMilliseconds()); + json.writeNumberField("percentileBucketCount", metricsConfig.getRollingCounterNumberOfBuckets()); + json.writeBooleanField("percentileEnabled", metricsConfig.isRollingPercentileEnabled()); + json.writeNumberField("counterBucketSizeInMilliseconds", metricsConfig.getRollingCounterBucketSizeInMilliseconds()); + json.writeNumberField("counterBucketCount", metricsConfig.getRollingCounterNumberOfBuckets()); + json.writeEndObject(); + json.writeObjectFieldStart("circuitBreaker"); + HystrixCommandConfiguration.HystrixCommandCircuitBreakerConfig circuitBreakerConfig = commandConfig.getCircuitBreakerConfig(); + json.writeBooleanField("enabled", circuitBreakerConfig.isEnabled()); + json.writeBooleanField("isForcedOpen", circuitBreakerConfig.isForceOpen()); + json.writeBooleanField("isForcedClosed", circuitBreakerConfig.isForceOpen()); + json.writeNumberField("requestVolumeThreshold", circuitBreakerConfig.getRequestVolumeThreshold()); + json.writeNumberField("errorPercentageThreshold", circuitBreakerConfig.getErrorThresholdPercentage()); + json.writeNumberField("sleepInMilliseconds", circuitBreakerConfig.getSleepWindowInMilliseconds()); + json.writeEndObject(); + json.writeEndObject(); + } + + private static void writeThreadPoolConfigJson(JsonGenerator json, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolConfiguration threadPoolConfig) throws IOException { + json.writeObjectFieldStart(threadPoolKey.name()); + json.writeNumberField("coreSize", threadPoolConfig.getCoreSize()); + json.writeNumberField("maximumSize", threadPoolConfig.getMaximumSize()); + json.writeNumberField("actualMaximumSize", threadPoolConfig.getActualMaximumSize()); + json.writeNumberField("maxQueueSize", threadPoolConfig.getMaxQueueSize()); + json.writeNumberField("queueRejectionThreshold", threadPoolConfig.getQueueRejectionThreshold()); + json.writeNumberField("keepAliveTimeInMinutes", threadPoolConfig.getKeepAliveTimeInMinutes()); + json.writeBooleanField("allowMaximumSizeToDivergeFromCoreSize", threadPoolConfig.getAllowMaximumSizeToDivergeFromCoreSize()); + json.writeNumberField("counterBucketSizeInMilliseconds", threadPoolConfig.getRollingCounterBucketSizeInMilliseconds()); + json.writeNumberField("counterBucketCount", threadPoolConfig.getRollingCounterNumberOfBuckets()); + json.writeEndObject(); + } + + private static void writeCollapserConfigJson(JsonGenerator json, HystrixCollapserKey collapserKey, HystrixCollapserConfiguration collapserConfig) throws IOException { + json.writeObjectFieldStart(collapserKey.name()); + json.writeNumberField("maxRequestsInBatch", collapserConfig.getMaxRequestsInBatch()); + json.writeNumberField("timerDelayInMilliseconds", collapserConfig.getTimerDelayInMilliseconds()); + json.writeBooleanField("requestCacheEnabled", collapserConfig.isRequestCacheEnabled()); + json.writeObjectFieldStart("metrics"); + HystrixCollapserConfiguration.CollapserMetricsConfig metricsConfig = collapserConfig.getCollapserMetricsConfig(); + json.writeNumberField("percentileBucketSizeInMilliseconds", metricsConfig.getRollingPercentileBucketSizeInMilliseconds()); + json.writeNumberField("percentileBucketCount", metricsConfig.getRollingCounterNumberOfBuckets()); + json.writeBooleanField("percentileEnabled", metricsConfig.isRollingPercentileEnabled()); + json.writeNumberField("counterBucketSizeInMilliseconds", metricsConfig.getRollingCounterBucketSizeInMilliseconds()); + json.writeNumberField("counterBucketCount", metricsConfig.getRollingCounterNumberOfBuckets()); + json.writeEndObject(); + json.writeEndObject(); + } + + public static String convertToString(HystrixConfiguration config) throws IOException { + StringWriter jsonString = new StringWriter(); + JsonGenerator json = jsonFactory.createGenerator(jsonString); + + json.writeStartObject(); + json.writeStringField("type", "HystrixConfig"); + json.writeObjectFieldStart("commands"); + for (Map.Entry entry: config.getCommandConfig().entrySet()) { + final HystrixCommandKey key = entry.getKey(); + final HystrixCommandConfiguration commandConfig = entry.getValue(); + writeCommandConfigJson(json, key, commandConfig); + + } + json.writeEndObject(); + + json.writeObjectFieldStart("threadpools"); + for (Map.Entry entry: config.getThreadPoolConfig().entrySet()) { + final HystrixThreadPoolKey threadPoolKey = entry.getKey(); + final HystrixThreadPoolConfiguration threadPoolConfig = entry.getValue(); + writeThreadPoolConfigJson(json, threadPoolKey, threadPoolConfig); + } + json.writeEndObject(); + + json.writeObjectFieldStart("collapsers"); + for (Map.Entry entry: config.getCollapserConfig().entrySet()) { + final HystrixCollapserKey collapserKey = entry.getKey(); + final HystrixCollapserConfiguration collapserConfig = entry.getValue(); + writeCollapserConfigJson(json, collapserKey, collapserConfig); + } + json.writeEndObject(); + json.writeEndObject(); + json.close(); + + return jsonString.getBuffer().toString(); + } + + /** + * @deprecated Not for public use. Using the delay param prevents streams from being efficiently shared. + * Please use {@link HystrixConfigurationStream#observe()} + * @param delay interval between data emissions + * @return sampled utilization as Java object, taken on a timer + */ + @Deprecated //deprecated in 1.5.4 + public Observable observe(int delay) { + return streamGenerator.call(delay); + } + + /** + * @deprecated Not for public use. Using the delay param prevents streams from being efficiently shared. + * Please use {@link HystrixConfigurationStream#observe()} + * and you can map to JSON string via {@link HystrixConfigurationJsonStream#convertToString(HystrixConfiguration)} + * @param delay interval between data emissions + * @return sampled utilization as JSON string, taken on a timer + */ + @Deprecated //deprecated in 1.5.4 + public Observable observeJson(int delay) { + return streamGenerator.call(delay).map(convertToJson); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixSampleSseServlet.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixSampleSseServlet.java new file mode 100644 index 0000000..a0e954f --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixSampleSseServlet.java @@ -0,0 +1,196 @@ +/** + * Copyright 2016 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.contrib.sample.stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.schedulers.Schedulers; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + */ +public abstract class HystrixSampleSseServlet extends HttpServlet { + protected final Observable sampleStream; + + private static final Logger logger = LoggerFactory.getLogger(HystrixSampleSseServlet.class); + + //wake up occasionally and check that poller is still alive. this value controls how often + protected static final int DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS = 500; + + private final int pausePollerThreadDelayInMs; + + /* response is not thread-safe */ + private final Object responseWriteLock = new Object(); + + /* Set to true upon shutdown, so it's OK to be shared among all SampleSseServlets */ + private static volatile boolean isDestroyed = false; + + protected HystrixSampleSseServlet(Observable sampleStream) { + this.sampleStream = sampleStream; + this.pausePollerThreadDelayInMs = DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS; + } + + protected HystrixSampleSseServlet(Observable sampleStream, int pausePollerThreadDelayInMs) { + this.sampleStream = sampleStream; + this.pausePollerThreadDelayInMs = pausePollerThreadDelayInMs; + } + + protected abstract int getMaxNumberConcurrentConnectionsAllowed(); + + protected abstract int getNumberCurrentConnections(); + + protected abstract int incrementAndGetCurrentConcurrentConnections(); + + protected abstract void decrementCurrentConcurrentConnections(); + + /** + * Handle incoming GETs + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if (isDestroyed) { + response.sendError(503, "Service has been shut down."); + } else { + handleRequest(request, response); + } + } + + /** + * WebSphere won't shutdown a servlet until after a 60 second timeout if there is an instance of the servlet executing + * a request. Add this method to enable a hook to notify Hystrix to shutdown. You must invoke this method at + * shutdown, perhaps from some other servlet's destroy() method. + */ + public static void shutdown() { + isDestroyed = true; + } + + @Override + public void init() throws ServletException { + isDestroyed = false; + } + + /** + * Handle servlet being undeployed by gracefully releasing connections so poller threads stop. + */ + @Override + public void destroy() { + /* set marker so the loops can break out */ + isDestroyed = true; + super.destroy(); + } + + /** + * - maintain an open connection with the client + * - on initial connection send latest data of each requested event type + * - subsequently send all changes for each requested event type + * + * @param request incoming HTTP Request + * @param response outgoing HTTP Response (as a streaming response) + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + private void handleRequest(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { + final AtomicBoolean moreDataWillBeSent = new AtomicBoolean(true); + Subscription sampleSubscription = null; + + /* ensure we aren't allowing more connections than we want */ + int numberConnections = incrementAndGetCurrentConcurrentConnections(); + try { + int maxNumberConnectionsAllowed = getMaxNumberConcurrentConnectionsAllowed(); //may change at runtime, so look this up for each request + if (numberConnections > maxNumberConnectionsAllowed) { + response.sendError(503, "MaxConcurrentConnections reached: " + maxNumberConnectionsAllowed); + } else { + /* initialize response */ + response.setHeader("Content-Type", "text/event-stream;charset=UTF-8"); + response.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"); + response.setHeader("Pragma", "no-cache"); + + final PrintWriter writer = response.getWriter(); + + //since the sample stream is based on Observable.interval, events will get published on an RxComputation thread + //since writing to the servlet response is blocking, use the Rx IO thread for the write that occurs in the onNext + sampleSubscription = sampleStream + .observeOn(Schedulers.io()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + logger.error("HystrixSampleSseServlet: ({}) received unexpected OnCompleted from sample stream", getClass().getSimpleName()); + moreDataWillBeSent.set(false); + } + + @Override + public void onError(Throwable e) { + moreDataWillBeSent.set(false); + } + + @Override + public void onNext(String sampleDataAsString) { + if (sampleDataAsString != null) { + try { + // avoid concurrent writes with ping + synchronized (responseWriteLock) { + writer.print("data: " + sampleDataAsString + "\n\n"); + // explicitly check for client disconnect - PrintWriter does not throw exceptions + if (writer.checkError()) { + moreDataWillBeSent.set(false); + } + writer.flush(); + } + } catch (Exception ex) { + moreDataWillBeSent.set(false); + } + } + } + }); + + while (moreDataWillBeSent.get() && !isDestroyed) { + try { + Thread.sleep(pausePollerThreadDelayInMs); + //in case stream has not started emitting yet, catch any clients which connect/disconnect before emits start + + // avoid concurrent writes with sample + synchronized (responseWriteLock) { + writer.print("ping: \n\n"); + // explicitly check for client disconnect - PrintWriter does not throw exceptions + if (writer.checkError()) { + moreDataWillBeSent.set(false); + } + writer.flush(); + } + } catch (Exception ex) { + moreDataWillBeSent.set(false); + } + } + } + } finally { + decrementCurrentConcurrentConnections(); + if (sampleSubscription != null && !sampleSubscription.isUnsubscribed()) { + sampleSubscription.unsubscribe(); + } + } + } +} + diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixUtilizationJsonStream.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixUtilizationJsonStream.java new file mode 100644 index 0000000..c3ffe72 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixUtilizationJsonStream.java @@ -0,0 +1,138 @@ +/** + * Copyright 2016 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.contrib.sample.stream; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.metric.sample.HystrixCommandUtilization; +import com.netflix.hystrix.metric.sample.HystrixThreadPoolUtilization; +import com.netflix.hystrix.metric.sample.HystrixUtilization; +import com.netflix.hystrix.metric.sample.HystrixUtilizationStream; +import rx.Observable; +import rx.functions.Func1; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Map; + +/** + * Links HystrixUtilizationStream and JSON encoding. This may be consumed in a variety of ways: + * -such as- + *

    + *
  • {@link HystrixUtilizationSseServlet} for mapping a specific URL to this data as an SSE stream + *
  • Consumer of your choice that wants control over where to embed this stream + *
+ * @deprecated Instead, prefer mapping your preferred serialization on top of {@link HystrixUtilizationStream#observe()}. + */ +@Deprecated //since 1.5.4 +public class HystrixUtilizationJsonStream { + private final Func1> streamGenerator; + + private static final JsonFactory jsonFactory = new JsonFactory(); + + private static final Func1 convertToJsonFunc = new Func1() { + @Override + public String call(HystrixUtilization utilization) { + try { + return convertToJson(utilization); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + }; + + @Deprecated //since 1.5.4 + public HystrixUtilizationJsonStream() { + this.streamGenerator = new Func1>() { + @Override + public Observable call(Integer delay) { + return HystrixUtilizationStream.getInstance().observe(); + } + }; + } + + @Deprecated //since 1.5.4 + public HystrixUtilizationJsonStream(Func1> streamGenerator) { + this.streamGenerator = streamGenerator; + } + + private static void writeCommandUtilizationJson(JsonGenerator json, HystrixCommandKey key, HystrixCommandUtilization utilization) throws IOException { + json.writeObjectFieldStart(key.name()); + json.writeNumberField("activeCount", utilization.getConcurrentCommandCount()); + json.writeEndObject(); + } + + private static void writeThreadPoolUtilizationJson(JsonGenerator json, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolUtilization utilization) throws IOException { + json.writeObjectFieldStart(threadPoolKey.name()); + json.writeNumberField("activeCount", utilization.getCurrentActiveCount()); + json.writeNumberField("queueSize", utilization.getCurrentQueueSize()); + json.writeNumberField("corePoolSize", utilization.getCurrentCorePoolSize()); + json.writeNumberField("poolSize", utilization.getCurrentPoolSize()); + json.writeEndObject(); + } + + protected static String convertToJson(HystrixUtilization utilization) throws IOException { + StringWriter jsonString = new StringWriter(); + JsonGenerator json = jsonFactory.createGenerator(jsonString); + + json.writeStartObject(); + json.writeStringField("type", "HystrixUtilization"); + json.writeObjectFieldStart("commands"); + for (Map.Entry entry: utilization.getCommandUtilizationMap().entrySet()) { + final HystrixCommandKey key = entry.getKey(); + final HystrixCommandUtilization commandUtilization = entry.getValue(); + writeCommandUtilizationJson(json, key, commandUtilization); + + } + json.writeEndObject(); + + json.writeObjectFieldStart("threadpools"); + for (Map.Entry entry: utilization.getThreadPoolUtilizationMap().entrySet()) { + final HystrixThreadPoolKey threadPoolKey = entry.getKey(); + final HystrixThreadPoolUtilization threadPoolUtilization = entry.getValue(); + writeThreadPoolUtilizationJson(json, threadPoolKey, threadPoolUtilization); + } + json.writeEndObject(); + json.writeEndObject(); + json.close(); + + return jsonString.getBuffer().toString(); + } + + /** + * @deprecated Not for public use. Using the delay param prevents streams from being efficiently shared. + * Please use {@link HystrixUtilizationStream#observe()} + * @param delay interval between data emissions + * @return sampled utilization as Java object, taken on a timer + */ + @Deprecated //deprecated as of 1.5.4 + public Observable observe(int delay) { + return streamGenerator.call(delay); + } + + /** + * @deprecated Not for public use. Using the delay param prevents streams from being efficiently shared. + * Please use {@link HystrixUtilizationStream#observe()} + * and the {@link #convertToJson(HystrixUtilization)} method + * @param delay interval between data emissions + * @return sampled utilization as JSON string, taken on a timer + */ + public Observable observeJson(int delay) { + return streamGenerator.call(delay).map(convertToJsonFunc); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixUtilizationSseServlet.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixUtilizationSseServlet.java new file mode 100644 index 0000000..c8ef483 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/main/java/com/netflix/hystrix/contrib/sample/stream/HystrixUtilizationSseServlet.java @@ -0,0 +1,91 @@ +/** + * Copyright 2016 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.contrib.sample.stream; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.metric.sample.HystrixUtilization; +import com.netflix.hystrix.metric.sample.HystrixUtilizationStream; +import com.netflix.hystrix.serial.SerialHystrixUtilization; +import rx.Observable; +import rx.functions.Func1; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Streams Hystrix config in text/event-stream format. + *

+ * Install by: + *

+ * 1) Including hystrix-metrics-event-stream-*.jar in your classpath. + *

+ * 2) Adding the following to web.xml: + *

{@code
+ * 
+ *  
+ *  HystrixUtilizationSseServlet
+ *  HystrixUtilizationSseServlet
+ *  com.netflix.hystrix.contrib.sample.stream.HystrixUtilizationSseServlet
+ * 
+ * 
+ *  HystrixUtilizationSseServlet
+ *  /hystrix/utilization.stream
+ * 
+ * } 
+ */ +public class HystrixUtilizationSseServlet extends HystrixSampleSseServlet { + + private static final long serialVersionUID = -7812908330777694972L; + + /* used to track number of connections and throttle */ + private static AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = + DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public HystrixUtilizationSseServlet() { + this(HystrixUtilizationStream.getInstance().observe(), DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS); + } + + /* package-private */ HystrixUtilizationSseServlet(Observable sampleStream, int pausePollerThreadDelayInMs) { + super(sampleStream.map(new Func1() { + @Override + public String call(HystrixUtilization hystrixUtilization) { + return SerialHystrixUtilization.toJsonString(hystrixUtilization); + } + }), pausePollerThreadDelayInMs); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + + @Override + protected int getNumberCurrentConnections() { + return concurrentConnections.get(); + } + + @Override + protected int incrementAndGetCurrentConcurrentConnections() { + return concurrentConnections.incrementAndGet(); + } + + @Override + protected void decrementCurrentConcurrentConnections() { + concurrentConnections.decrementAndGet(); + } +} + diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsPollerTest.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsPollerTest.java new file mode 100644 index 0000000..4d6805a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsPollerTest.java @@ -0,0 +1,101 @@ +/** + * Copyright 2012 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.contrib.metrics.eventstream; + +import static org.junit.Assert.*; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * Polls Hystrix metrics and output JSON strings for each metric to a MetricsPollerListener. + *

+ * Polling can be stopped/started. Use shutdown() to permanently shutdown the poller. + */ +public class HystrixMetricsPollerTest { + + @Test + public void testStartStopStart() { + final AtomicInteger metricsCount = new AtomicInteger(); + + HystrixMetricsPoller poller = new HystrixMetricsPoller(new HystrixMetricsPoller.MetricsAsJsonPollerListener() { + + @Override + public void handleJsonMetric(String json) { + System.out.println("Received: " + json); + metricsCount.incrementAndGet(); + } + }, 100); + try { + + HystrixCommand test = new HystrixCommand(HystrixCommandGroupKey.Factory.asKey("HystrixMetricsPollerTest")) { + + @Override + protected Boolean run() { + return true; + } + + }; + test.execute(); + + poller.start(); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + int v1 = metricsCount.get(); + + assertTrue(v1 > 0); + + poller.pause(); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + int v2 = metricsCount.get(); + + // they should be the same since we were paused + System.out.println("First poll got : " + v1 + ", second got : " + v2); + assertTrue(v2 == v1); + + poller.start(); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + int v3 = metricsCount.get(); + + // we should have more metrics again + assertTrue(v3 > v1); + + } finally { + poller.shutdown(); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsStreamServletUnitTest.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsStreamServletUnitTest.java new file mode 100644 index 0000000..14049cf --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/metrics/eventstream/HystrixMetricsStreamServletUnitTest.java @@ -0,0 +1,84 @@ +/** + * 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.contrib.metrics.eventstream; + +import com.netflix.hystrix.metric.consumer.HystrixDashboardStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import rx.Observable; +import rx.functions.Func1; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class HystrixMetricsStreamServletUnitTest { + + @Mock HttpServletRequest mockReq; + @Mock HttpServletResponse mockResp; + @Mock HystrixDashboardStream.DashboardData mockDashboard; + @Mock PrintWriter mockPrintWriter; + + HystrixMetricsStreamServlet servlet; + + private final Observable streamOfOnNexts = + Observable.interval(100, TimeUnit.MILLISECONDS).map(new Func1() { + @Override + public HystrixDashboardStream.DashboardData call(Long timestamp) { + return mockDashboard; + } + }); + + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + when(mockReq.getMethod()).thenReturn("GET"); + } + + @After + public void tearDown() { + servlet.destroy(); + servlet.shutdown(); + } + + @Test + public void shutdownServletShouldRejectRequests() throws ServletException, IOException { + servlet = new HystrixMetricsStreamServlet(streamOfOnNexts, 10); + try { + servlet.init(); + } catch (ServletException ex) { + + } + + servlet.shutdown(); + + servlet.service(mockReq, mockResp); + + verify(mockResp).sendError(503, "Service has been shut down."); + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigSseServletTest.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigSseServletTest.java new file mode 100644 index 0000000..27b9ad3 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/sample/stream/HystrixConfigSseServletTest.java @@ -0,0 +1,334 @@ +/** + * Copyright 2016 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.contrib.sample.stream; + +import com.netflix.hystrix.config.HystrixConfiguration; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import rx.Observable; +import rx.Subscriber; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class HystrixConfigSseServletTest { + + @Mock HttpServletRequest mockReq; + @Mock HttpServletResponse mockResp; + @Mock HystrixConfiguration mockConfig; + @Mock PrintWriter mockPrintWriter; + + HystrixConfigSseServlet servlet; + + private final Observable streamOfOnNexts = Observable.interval(100, TimeUnit.MILLISECONDS).map(new Func1() { + @Override + public HystrixConfiguration call(Long timestamp) { + return mockConfig; + } + }); + + private final Observable streamOfOnNextThenOnError = Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + Thread.sleep(100); + subscriber.onNext(mockConfig); + Thread.sleep(100); + subscriber.onError(new RuntimeException("stream failure")); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + }).subscribeOn(Schedulers.computation()); + + private final Observable streamOfOnNextThenOnCompleted = Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + Thread.sleep(100); + subscriber.onNext(mockConfig); + Thread.sleep(100); + subscriber.onCompleted(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + }).subscribeOn(Schedulers.computation()); + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + } + + @After + public void tearDown() { + servlet.destroy(); + servlet.shutdown(); + } + + @Test + public void shutdownServletShouldRejectRequests() throws ServletException, IOException { + servlet = new HystrixConfigSseServlet(streamOfOnNexts, 10); + try { + servlet.init(); + } catch (ServletException ex) { + + } + + servlet.shutdown(); + + servlet.doGet(mockReq, mockResp); + + verify(mockResp).sendError(503, "Service has been shut down."); + } + + @Test + public void testConfigDataWithInfiniteOnNextStream() throws IOException, InterruptedException { + servlet = new HystrixConfigSseServlet(streamOfOnNexts, 10); + try { + servlet.init(); + } catch (ServletException ex) { + + } + + final AtomicInteger writes = new AtomicInteger(0); + + when(mockReq.getParameter("delay")).thenReturn("100"); + when(mockResp.getWriter()).thenReturn(mockPrintWriter); + Mockito.doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + String written = (String) invocation.getArguments()[0]; + System.out.println("ARG : " + written); + + if (!written.contains("ping")) { + writes.incrementAndGet(); + } + return null; + } + }).when(mockPrintWriter).print(Mockito.anyString()); + + Runnable simulateClient = new Runnable() { + @Override + public void run() { + try { + servlet.doGet(mockReq, mockResp); + } catch (ServletException ex) { + fail(ex.getMessage()); + } catch (IOException ex) { + fail(ex.getMessage()); + } + } + }; + + Thread t = new Thread(simulateClient); + System.out.println("Starting thread : " + t.getName()); + t.start(); + System.out.println("Started thread : " + t.getName()); + + try { + Thread.sleep(1000); + System.out.println("Woke up from sleep : " + Thread.currentThread().getName()); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + System.out.println("About to interrupt"); + t.interrupt(); + System.out.println("Done interrupting"); + + Thread.sleep(100); + + System.out.println("WRITES : " + writes.get()); + assertTrue(writes.get() >= 9); + assertEquals(0, servlet.getNumberCurrentConnections()); + } + + @Test + public void testConfigDataWithStreamOnError() throws IOException, InterruptedException { + servlet = new HystrixConfigSseServlet(streamOfOnNextThenOnError, 10); + try { + servlet.init(); + } catch (ServletException ex) { + + } + + final AtomicInteger writes = new AtomicInteger(0); + + when(mockReq.getParameter("delay")).thenReturn("100"); + when(mockResp.getWriter()).thenReturn(mockPrintWriter); + Mockito.doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + String written = (String) invocation.getArguments()[0]; + System.out.println("ARG : " + written); + + if (!written.contains("ping")) { + writes.incrementAndGet(); + } + return null; + } + }).when(mockPrintWriter).print(Mockito.anyString()); + + Runnable simulateClient = new Runnable() { + @Override + public void run() { + try { + servlet.doGet(mockReq, mockResp); + } catch (ServletException ex) { + fail(ex.getMessage()); + } catch (IOException ex) { + fail(ex.getMessage()); + } + } + }; + + Thread t = new Thread(simulateClient); + t.start(); + + try { + Thread.sleep(1000); + System.out.println(System.currentTimeMillis() + " Woke up from sleep : " + Thread.currentThread().getName()); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + assertEquals(1, writes.get()); + assertEquals(0, servlet.getNumberCurrentConnections()); + } + + @Test + public void testConfigDataWithStreamOnCompleted() throws IOException, InterruptedException { + servlet = new HystrixConfigSseServlet(streamOfOnNextThenOnCompleted, 10); + try { + servlet.init(); + } catch (ServletException ex) { + + } + + final AtomicInteger writes = new AtomicInteger(0); + + when(mockReq.getParameter("delay")).thenReturn("100"); + when(mockResp.getWriter()).thenReturn(mockPrintWriter); + Mockito.doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + String written = (String) invocation.getArguments()[0]; + System.out.println("ARG : " + written); + + if (!written.contains("ping")) { + writes.incrementAndGet(); + } + return null; + } + }).when(mockPrintWriter).print(Mockito.anyString()); + + Runnable simulateClient = new Runnable() { + @Override + public void run() { + try { + servlet.doGet(mockReq, mockResp); + } catch (ServletException ex) { + fail(ex.getMessage()); + } catch (IOException ex) { + fail(ex.getMessage()); + } + } + }; + + Thread t = new Thread(simulateClient); + t.start(); + + try { + Thread.sleep(1000); + System.out.println(System.currentTimeMillis() + " Woke up from sleep : " + Thread.currentThread().getName()); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + assertEquals(1, writes.get()); + assertEquals(0, servlet.getNumberCurrentConnections()); + } + + @Test + public void testConfigDataWithIoExceptionOnWrite() throws IOException, InterruptedException { + servlet = new HystrixConfigSseServlet(streamOfOnNexts, 10); + try { + servlet.init(); + } catch (ServletException ex) { + + } + + final AtomicInteger writes = new AtomicInteger(0); + + when(mockResp.getWriter()).thenReturn(mockPrintWriter); + Mockito.doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + String written = (String) invocation.getArguments()[0]; + System.out.println("ARG : " + written); + + if (!written.contains("ping")) { + writes.incrementAndGet(); + } + throw new IOException("simulated IO Exception"); + } + }).when(mockPrintWriter).print(Mockito.anyString()); + + Runnable simulateClient = new Runnable() { + @Override + public void run() { + try { + servlet.doGet(mockReq, mockResp); + } catch (ServletException ex) { + fail(ex.getMessage()); + } catch (IOException ex) { + fail(ex.getMessage()); + } + } + }; + + Thread t = new Thread(simulateClient); + t.start(); + + try { + Thread.sleep(1000); + System.out.println(System.currentTimeMillis() + " Woke up from sleep : " + Thread.currentThread().getName()); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + assertTrue(writes.get() <= 2); + assertEquals(0, servlet.getNumberCurrentConnections()); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/sample/stream/HystrixSampleSseServletTest.java b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/sample/stream/HystrixSampleSseServletTest.java new file mode 100644 index 0000000..f296153 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-metrics-event-stream/src/test/java/com/netflix/hystrix/contrib/sample/stream/HystrixSampleSseServletTest.java @@ -0,0 +1,185 @@ +/** + * Copyright 2016 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.contrib.sample.stream; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.config.HystrixConfiguration; +import com.netflix.hystrix.config.HystrixConfigurationStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import rx.Observable; +import rx.Subscriber; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +public class HystrixSampleSseServletTest { + + private static final String INTERJECTED_CHARACTER = "a"; + + @Mock HttpServletRequest mockReq; + @Mock HttpServletResponse mockResp; + @Mock HystrixConfiguration mockConfig; + @Mock PrintWriter mockPrintWriter; + + TestHystrixConfigSseServlet servlet; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + } + + @After + public void tearDown() { + servlet.destroy(); + servlet.shutdown(); + } + + @Test + public void testNoConcurrentResponseWrites() throws IOException, InterruptedException { + final Observable limitedOnNexts = Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + for (int i = 0; i < 500; i++) { + Thread.sleep(10); + subscriber.onNext(mockConfig); + } + + } catch (InterruptedException ex) { + ex.printStackTrace(); + } catch (Exception e) { + subscriber.onCompleted(); + } + } + }).subscribeOn(Schedulers.computation()); + + servlet = new TestHystrixConfigSseServlet(limitedOnNexts, 1); + try { + servlet.init(); + } catch (ServletException ex) { + + } + + final StringBuilder buffer = new StringBuilder(); + + when(mockReq.getParameter("delay")).thenReturn("100"); + when(mockResp.getWriter()).thenReturn(mockPrintWriter); + Mockito.doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + String written = (String) invocation.getArguments()[0]; + if (written.contains("ping")) { + buffer.append(INTERJECTED_CHARACTER); + } else { + // slow down the append to increase chances to interleave + for (int i = 0; i < written.length(); i++) { + Thread.sleep(5); + buffer.append(written.charAt(i)); + } + } + return null; + } + }).when(mockPrintWriter).print(Mockito.anyString()); + + Runnable simulateClient = new Runnable() { + @Override + public void run() { + try { + servlet.doGet(mockReq, mockResp); + } catch (ServletException ex) { + fail(ex.getMessage()); + } catch (IOException ex) { + fail(ex.getMessage()); + } + } + }; + + Thread t = new Thread(simulateClient); + t.start(); + + try { + Thread.sleep(1000); + System.out.println(System.currentTimeMillis() + " Woke up from sleep : " + Thread.currentThread().getName()); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + Pattern pattern = Pattern.compile("\\{[" + INTERJECTED_CHARACTER + "]+\\}"); + boolean hasInterleaved = pattern.matcher(buffer).find(); + assertFalse(hasInterleaved); + } + + private static class TestHystrixConfigSseServlet extends HystrixSampleSseServlet { + + private static AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5); + + public TestHystrixConfigSseServlet() { + this(HystrixConfigurationStream.getInstance().observe(), DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS); + } + + TestHystrixConfigSseServlet(Observable sampleStream, int pausePollerThreadDelayInMs) { + super(sampleStream.map(new Func1() { + @Override + public String call(HystrixConfiguration hystrixConfiguration) { + return "{}"; + } + }), pausePollerThreadDelayInMs); + } + + @Override + protected int getMaxNumberConcurrentConnectionsAllowed() { + return maxConcurrentConnections.get(); + } + + @Override + protected int getNumberCurrentConnections() { + return concurrentConnections.get(); + } + + @Override + protected int incrementAndGetCurrentConcurrentConnections() { + return concurrentConnections.incrementAndGet(); + } + + @Override + protected void decrementCurrentConcurrentConnections() { + concurrentConnections.decrementAndGet(); + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/README.md b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/README.md new file mode 100644 index 0000000..0a2c254 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/README.md @@ -0,0 +1,79 @@ +# Hystrix Network Auditor Agent + +Original Issue: https://github.com/Netflix/Hystrix/issues/116 + +This is a [Java Agent](http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html) that instruments network access to allow auditing whether it is being performed within the context of Hystrix or not. + +The intent is to enable building a listener to: + +- find unknown vulnerabilities (network traffic that was unknown) +- track "drift" over time as transitive dependencies pull in code that performs network access +- track metrics and optionally provide alerts +- allow failing a canary build if unexpected network access occurs +- alert in production if unexpected network access starts (such as if a property is flipped to turn on a feature) + + +# Why? + +This module originates out of work to deal with the "unknowns" that continue to cause failure after wrapping all known network access in Hystrix. + +The Netflix API team successfully eliminated a large percentage of the class of errors and outages caused by network latency to backend services but maintaining this state of protection is difficult with constantly changing code and 3rd party libraries. + +In other words it's easy to either be unaware of a vulnerability or "[drift into failure](http://www.amazon.com/Drift-into-Failure-ebook/dp/B009KOKXKY/ref=tmm_kin_title_0)" as the state of the system changes over time. + + +# How to Use + +1) Enable the Java Agent + +Add the following to the JVM command-line: + +``` +-javaagent:/var/root/hystrix-network-auditor-agent-x.y.zjar +``` + +This will be loaded in the boot classloader and instrument `java.net` and `java.io` classes. + +2) Register an Event Listener + +In the application register a listener to be invoked on each network event: + +```java +com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent.registerEventListener(eventListener) +``` + +3) Handle Events + +It is up to the application to decide what to do but generally it is expected that an implementation will filter to only calls not wrapped in Hystrix and then determine the stack trace so the code path can be identified. + +Here is an example that increments an overall counter and records the stack trace with a counter per unique stack trace: + +```java + @Override + public void handleNetworkEvent() { + if (Hystrix.getCurrentThreadExecutingCommand() == null) { + // increment counter + totalNonIsolatedEventsCounter.increment(); + // capture the stacktrace and record so network events can be debugged and tracked down + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + HystrixNetworkEvent counter = counters.get(stack); + if (counter == null) { + counter = new HystrixNetworkEvent(stack); + HystrixNetworkEvent c = counters.putIfAbsent(Arrays.toString(stack), counter); + if (c != null) { + // another thread beat us + counter = c; + } + } + counter.increment(); + } + } +``` + +# Deployment Model + +This is not expected to run on all production instances but as part of a canary process. + +For example the Netflix API team intends to have long-running canaries using this agent and treat it like "canaries in the coalmine" that are always running and alert us if network traffic shows up that we are not aware of and not wrapped by Hystrix. + +More information about experience will come over time ... this is open-source so we're developing in public! diff --git a/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/build.gradle new file mode 100644 index 0000000..827f48a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/build.gradle @@ -0,0 +1,16 @@ +dependencies { + compileApi 'org.javassist:javassist:3.19+' + + jar { + // make a fatjar otherwise it's painful getting the boot-class-path correct when deploying + from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + manifest { + attributes( + "Agent-Class": "com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent", + "Can-Redefine-Classes": true, + "Can-Retransform-Classes": true, + "Boot-Class-Path": "hystrix-network-auditor-agent-" + version + ".jar", + "Premain-Class": "com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent") + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/HystrixNetworkAuditorAgent.java b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/HystrixNetworkAuditorAgent.java new file mode 100644 index 0000000..48f6296 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/HystrixNetworkAuditorAgent.java @@ -0,0 +1,59 @@ +/** + * Copyright 2013 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.contrib.networkauditor; + +import java.lang.instrument.Instrumentation; + +/** + * Java Agent to instrument network code in the java.* libraries and use Hystrix state to determine if calls are Hystrix-isolated or not. + */ +public class HystrixNetworkAuditorAgent { + + private static final HystrixNetworkAuditorEventListener DEFAULT_BRIDGE = new HystrixNetworkAuditorEventListener() { + + @Override + public void handleNetworkEvent() { + // do nothing + } + + }; + private static volatile HystrixNetworkAuditorEventListener bridge = DEFAULT_BRIDGE; + + public static void premain(String agentArgs, Instrumentation inst) { + inst.addTransformer(new NetworkClassTransform()); + } + + /** + * Invoked by instrumented code when network access occurs. + */ + public static void notifyOfNetworkEvent() { + bridge.handleNetworkEvent(); + } + + /** + * Register the implementation of the {@link HystrixNetworkAuditorEventListener} that will receive the events. + * + * @param bridge + * {@link HystrixNetworkAuditorEventListener} implementation + */ + public static void registerEventListener(HystrixNetworkAuditorEventListener bridge) { + if (bridge == null) { + throw new IllegalArgumentException("HystrixNetworkAuditorEventListener can not be NULL"); + } + HystrixNetworkAuditorAgent.bridge = bridge; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/HystrixNetworkAuditorEventListener.java b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/HystrixNetworkAuditorEventListener.java new file mode 100644 index 0000000..713939d --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/HystrixNetworkAuditorEventListener.java @@ -0,0 +1,45 @@ +/** + * Copyright 2013 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.contrib.networkauditor; + +/** + * Event listener that gets implemented and registered with {@link HystrixNetworkAuditorAgent#registerEventListener(HystrixNetworkAuditorEventListener)} by the application + * running Hystrix in the application classloader into this JavaAgent running in the boot classloader so events can be invoked on code inside the application classloader. + */ +public interface HystrixNetworkAuditorEventListener { + + /** + * Invoked by the {@link HystrixNetworkAuditorAgent} when network events occur. + *

+ * An event may be the opening of a socket, channel or reading/writing bytes. It is not guaranteed to be a one-to-one relationship with a request/response. + *

+ * No arguments are returned as it is left to the implementation to decide whether the current thread stacktrace should be captured or not. + *

+ * Typical implementations will want to filter to non-Hystrix isolated network traffic with code such as this: + * + *

 {@code
+     * 
+     * if (Hystrix.getCurrentThreadExecutingCommand() == null) {
+     *      // this event is not inside a Hystrix context (according to ThreadLocal variables)
+     *      StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+     *      // increment counters, fire alerts, log stack traces etc
+     * }
+     * } 
+ * + */ + public void handleNetworkEvent(); + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/NetworkClassTransform.java b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/NetworkClassTransform.java new file mode 100644 index 0000000..b3856fc --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-network-auditor-agent/src/main/java/com/netflix/hystrix/contrib/networkauditor/NetworkClassTransform.java @@ -0,0 +1,115 @@ +/** + * Copyright 2013 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.contrib.networkauditor; + +import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; + +/** + * Bytecode ClassFileTransformer used by the Java Agent to instrument network code in the java.* libraries and use Hystrix state to determine if calls are Hystrix-isolated or not. + */ +public class NetworkClassTransform implements ClassFileTransformer { + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + String name = className.replace('/', '.'); + try { + /* + * These hook points were found through trial-and-error after wrapping all java.net/java.io/java.nio classes and methods and finding reliable + * notifications that matched statistics and events in an application. + * + * There may very well be problems here or code paths that don't correctly trigger an event. + * + * If someone can provide a more reliable and cleaner hook point or if there are examples of code paths that don't trigger either of these + * then please file a bug or submit a pull request at https://github.com/Netflix/Hystrix + */ + if (name.equals("java.net.Socket$2")) { + // this one seems to be fairly reliable in counting each time a request/response occurs on blocking IO + return wrapConstructorsOfClass(name); + } else if (name.equals("java.nio.channels.SocketChannel")) { + // handle NIO + return wrapConstructorsOfClass(name); + } + } catch (Exception e) { + throw new RuntimeException("Failed trying to wrap class: " + className, e); + } + + // we didn't transform anything so return null to leave untouched + return null; + } + + /** + * Wrap all constructors of a given class + * + * @param className + * @throws NotFoundException + * @throws CannotCompileException + * @throws IOException + */ + private byte[] wrapConstructorsOfClass(String className) throws NotFoundException, IOException, CannotCompileException { + return wrapClass(className, true); + } + + /** + * Wrap all signatures of a given method name. + * + * @param className + * @param methodName + * @throws NotFoundException + * @throws CannotCompileException + * @throws IOException + */ + private byte[] wrapClass(String className, boolean wrapConstructors, String... methodNames) throws NotFoundException, IOException, CannotCompileException { + ClassPool cp = ClassPool.getDefault(); + CtClass ctClazz = cp.get(className); + // constructors + if (wrapConstructors) { + CtConstructor[] constructors = ctClazz.getConstructors(); + for (CtConstructor constructor : constructors) { + try { + constructor.insertBefore("{ com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent.notifyOfNetworkEvent(); }"); + } catch (Exception e) { + throw new RuntimeException("Failed trying to wrap constructor of class: " + className, e); + } + + } + } + // methods + CtMethod[] methods = ctClazz.getDeclaredMethods(); + for (CtMethod method : methods) { + try { + for (String methodName : methodNames) { + if (method.getName().equals(methodName)) { + method.insertBefore("{ com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent.handleNetworkEvent(); }"); + } + } + } catch (Exception e) { + throw new RuntimeException("Failed trying to wrap method [" + method.getName() + "] of class: " + className, e); + } + } + return ctClazz.toBytecode(); + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-request-servlet/README.md b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/README.md new file mode 100644 index 0000000..c558b59 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/README.md @@ -0,0 +1,85 @@ +# Hystrix Request Servlet Filters + +This module contains functional examples for a J2EE/Servlet environment that initialize and uses [HystrixRequestContext](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestContext.java). + +You can use this module as is or model your own implementation after it as these classes are very basic. + +If using a framework that doesn't use Servlets, or a framework with other lifecycle hooks you may need to implement your own anyways. + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-request-servlet%22). + +Example for Maven: + +```xml + + com.netflix.hystrix + hystrix-request-servlet + 1.1.2 + +``` + +and for Ivy: + +```xml + +``` + +# Installation + +## [HystrixRequestContextServletFilter](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestContextServletFilter.java) + +This initializes the [HystrixRequestContext](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestContext.java) at the beginning of each HTTP request and then cleans it up at the end. + +You install it by adding the following to your web.xml: + +```xml + + HystrixRequestContextServletFilter + HystrixRequestContextServletFilter + com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter + + + HystrixRequestContextServletFilter + /* + +``` + +### [HystrixRequestLogViaLoggerServletFilter](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestLogViaLoggerServletFilter.java) + +This logs an INFO message with the output from [HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/HystrixRequestLog.html#getExecutedCommandsAsString(\)) at the end of each requet. + +You install it by adding the following to your web.xml: + +```xml + + HystrixRequestLogViaLoggerServletFilter + HystrixRequestLogViaLoggerServletFilter + com.netflix.hystrix.contrib.requestservlet.HystrixRequestLogViaLoggerServletFilter + + + HystrixRequestLogViaLoggerServletFilter + /* + +``` + + +### [HystrixRequestLogViaResponseHeaderServletFilter](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestLogViaResponseHeaderServletFilter.java) + +This adds the output of [HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/HystrixRequestLog.html#getExecutedCommandsAsString(\)) to the HTTP response as header "X-HystrixLog". + +Note that this will not work if the response has been flushed already (such as on a progressively rendered page). + +```xml + + HystrixRequestLogViaResponseHeaderServletFilter + HystrixRequestLogViaResponseHeaderServletFilter + com.netflix.hystrix.contrib.requestservlet.HystrixRequestLogViaResponseHeaderServletFilter + + + HystrixRequestLogViaResponseHeaderServletFilter + /* + +``` + diff --git a/Hystrix-master/hystrix-contrib/hystrix-request-servlet/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/build.gradle new file mode 100644 index 0000000..db6418a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compileApi project(':hystrix-core') + provided 'javax.servlet:servlet-api:2.5' +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestContextServletFilter.java b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestContextServletFilter.java new file mode 100644 index 0000000..573499f --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestContextServletFilter.java @@ -0,0 +1,68 @@ +/** + * Copyright 2012 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.contrib.requestservlet; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Initializes the {@link HystrixRequestContext} at the beginning of each HTTP request and then cleans it up at the end. + *

+ * Install by adding the following lines to your project web.xml: + *

+ * + *

+ * {@code
+ *   
+ *     HystrixRequestContextServletFilter
+ *     HystrixRequestContextServletFilter
+ *     com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter
+ *   
+ *   
+ *     HystrixRequestContextServletFilter
+ *     /*
+ *   
+ * }
+ * 
+ */ +public class HystrixRequestContextServletFilter implements Filter { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + chain.doFilter(request, response); + } finally { + context.shutdown(); + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void destroy() { + + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestLogViaLoggerServletFilter.java b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestLogViaLoggerServletFilter.java new file mode 100644 index 0000000..c34fa78 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestLogViaLoggerServletFilter.java @@ -0,0 +1,101 @@ +/** + * Copyright 2012 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.contrib.requestservlet; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Log an INFO message with the output from HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString() at the end of each requet. + *

+ * A pre-requisite is that {@link HystrixRequestContext} is initialized, such as by using {@link HystrixRequestContextServletFilter}. + *

+ * Install by adding the following lines to your project web.xml: + *

+ * + *

+ * {@code
+ *   
+ *     HystrixRequestLogViaLoggerServletFilter
+ *     HystrixRequestLogViaLoggerServletFilter
+ *     com.netflix.hystrix.contrib.requestservlet.HystrixRequestLogViaLoggerServletFilter
+ *   
+ *   
+ *     HystrixRequestLogViaLoggerServletFilter
+ *     /*
+ *   
+ * }
+ * 
+ *

+ * NOTE: This filter must complete before {@link HystrixRequestContext} is shutdown otherwise the {@link HystrixRequestLog} will already be cleared. + *

+ * This will output a log line similar to this: + * + *

+ * Hystrix Executions [POST /order] => CreditCardCommand[SUCCESS][1122ms]
+ * 
+ */ +public class HystrixRequestLogViaLoggerServletFilter implements Filter { + private static final Logger logger = LoggerFactory.getLogger(HystrixRequestLogViaLoggerServletFilter.class); + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + StringBuilder requestURL = new StringBuilder(); + try { + // get the requested URL for our logging so it can be associated to a particular request + String uri = ((HttpServletRequest) request).getRequestURI(); + String queryString = ((HttpServletRequest) request).getQueryString(); + String method = ((HttpServletRequest) request).getMethod(); + requestURL.append(method).append(" ").append(uri); + if (queryString != null) { + requestURL.append("?").append(queryString); + } + chain.doFilter(request, response); + } finally { + try { + if (HystrixRequestContext.isCurrentThreadInitialized()) { + HystrixRequestLog log = HystrixRequestLog.getCurrentRequest(); + logger.info("Hystrix Executions [{}] => {}", requestURL.toString(), log.getExecutedCommandsAsString()); + } + } catch (Exception e) { + logger.warn("Unable to append HystrixRequestLog", e); + } + + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void destroy() { + + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestLogViaResponseHeaderServletFilter.java b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestLogViaResponseHeaderServletFilter.java new file mode 100644 index 0000000..6b6c4f0 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-request-servlet/src/main/java/com/netflix/hystrix/contrib/requestservlet/HystrixRequestLogViaResponseHeaderServletFilter.java @@ -0,0 +1,88 @@ +/** + * Copyright 2012 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.contrib.requestservlet; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Add HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString() to response as header "X-HystrixLog". + *

+ * This will not work if the response has been flushed already. + *

+ * A pre-requisite is that {@link HystrixRequestContext} is initialized, such as by using {@link HystrixRequestContextServletFilter}. + *

+ * Install by adding the following lines to your project web.xml: + *

+ * + *

+ * {@code
+ *   
+ *     HystrixRequestLogViaResponseHeaderServletFilter
+ *     HystrixRequestLogViaResponseHeaderServletFilter
+ *     com.netflix.hystrix.contrib.requestservlet.HystrixRequestLogViaResponseHeaderServletFilter
+ *   
+ *   
+ *     HystrixRequestLogServletFilter
+ *     /*
+ *   
+ * }
+ * 
+ */ +public class HystrixRequestLogViaResponseHeaderServletFilter implements Filter { + private static final Logger logger = LoggerFactory.getLogger(HystrixRequestLogViaResponseHeaderServletFilter.class); + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + try { + chain.doFilter(request, response); + } finally { + try { + if (HystrixRequestContext.isCurrentThreadInitialized()) { + HystrixRequestLog log = HystrixRequestLog.getCurrentRequest(); + if (log != null) { + ((HttpServletResponse) response).addHeader("X-HystrixLog", log.getExecutedCommandsAsString()); + } + } + } catch (Exception e) { + logger.warn("Unable to append HystrixRequestLog", e); + } + + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void destroy() { + + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/build.gradle new file mode 100644 index 0000000..890df22 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/build.gradle @@ -0,0 +1,8 @@ +dependencies { + compileApi project(':hystrix-core') + compile project(':hystrix-serialization') + compileApi 'io.reactivex:rxnetty:0.4.17' + testCompile 'junit:junit-dep:4.10' + testCompile 'org.powermock:powermock-easymock-release-full:1.5.5' + testCompile 'org.easymock:easymock:3.2' +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/main/java/com/netflix/hystrix/contrib/rxnetty/metricsstream/HystrixMetricsStreamHandler.java b/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/main/java/com/netflix/hystrix/contrib/rxnetty/metricsstream/HystrixMetricsStreamHandler.java new file mode 100644 index 0000000..1c50acc --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/main/java/com/netflix/hystrix/contrib/rxnetty/metricsstream/HystrixMetricsStreamHandler.java @@ -0,0 +1,129 @@ +/** + * 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.contrib.rxnetty.metricsstream; + +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.serial.SerialHystrixDashboardData; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.UnpooledByteBufAllocator; +import io.reactivex.netty.protocol.http.server.HttpServerRequest; +import io.reactivex.netty.protocol.http.server.HttpServerResponse; +import io.reactivex.netty.protocol.http.server.RequestHandler; +import rx.Observable; +import rx.Subscription; +import rx.functions.Action1; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; +import rx.subscriptions.MultipleAssignmentSubscription; + +import java.nio.charset.Charset; +import java.util.concurrent.TimeUnit; + +/** + * Streams Hystrix metrics in Server Sent Event (SSE) format. RxNetty application handlers shall + * be wrapped by this handler. It transparently intercepts HTTP requests at a configurable path + * (default "/hystrix.stream"), and sends unbounded SSE streams back to the client. All other requests + * are transparently forwarded to the application handlers. + *

+ * For RxNetty client tapping into SSE stream: remember to use unpooled HTTP connections. If not, the pooled HTTP + * connection will not be closed on unsubscribe event and the event stream will continue to flow towards the client + * (unless the client is shutdown). + * + * @author Tomasz Bak + * @author Christian Schmitt + */ +public class HystrixMetricsStreamHandler implements RequestHandler { + + public static final String DEFAULT_HYSTRIX_PREFIX = "/hystrix.stream"; + + public static final int DEFAULT_INTERVAL = 2000; + + private static final byte[] HEADER = "data: ".getBytes(Charset.defaultCharset()); + private static final byte[] FOOTER = {10, 10}; + private static final int EXTRA_SPACE = HEADER.length + FOOTER.length; + + private final String hystrixPrefix; + private final long interval; + private final RequestHandler appHandler; + + public HystrixMetricsStreamHandler(RequestHandler appHandler) { + this(DEFAULT_HYSTRIX_PREFIX, DEFAULT_INTERVAL, appHandler); + } + + HystrixMetricsStreamHandler(String hystrixPrefix, long interval, RequestHandler appHandler) { + this.hystrixPrefix = hystrixPrefix; + this.interval = interval; + this.appHandler = appHandler; + } + + @Override + public Observable handle(HttpServerRequest request, HttpServerResponse response) { + if (request.getPath().startsWith(hystrixPrefix)) { + return handleHystrixRequest(response); + } + return appHandler.handle(request, response); + } + + private Observable handleHystrixRequest(final HttpServerResponse response) { + writeHeaders(response); + + final Subject subject = PublishSubject.create(); + final MultipleAssignmentSubscription subscription = new MultipleAssignmentSubscription(); + Subscription actionSubscription = Observable.interval(interval, TimeUnit.MILLISECONDS) + .subscribe(new Action1() { + @Override + public void call(Long tick) { + if (!response.getChannel().isOpen()) { + subscription.unsubscribe(); + return; + } + try { + for (HystrixCommandMetrics commandMetrics : HystrixCommandMetrics.getInstances()) { + writeMetric(SerialHystrixDashboardData.toJsonString(commandMetrics), response); + } + for (HystrixThreadPoolMetrics threadPoolMetrics : HystrixThreadPoolMetrics.getInstances()) { + writeMetric(SerialHystrixDashboardData.toJsonString(threadPoolMetrics), response); + } + for (HystrixCollapserMetrics collapserMetrics : HystrixCollapserMetrics.getInstances()) { + writeMetric(SerialHystrixDashboardData.toJsonString(collapserMetrics), response); + } + } catch (Exception e) { + subject.onError(e); + } + } + }); + subscription.set(actionSubscription); + return subject; + } + + private void writeHeaders(HttpServerResponse response) { + response.getHeaders().add("Content-Type", "text/event-stream;charset=UTF-8"); + response.getHeaders().add("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"); + response.getHeaders().add("Pragma", "no-cache"); + } + + @SuppressWarnings("unchecked") + private void writeMetric(String json, HttpServerResponse response) { + byte[] bytes = json.getBytes(Charset.defaultCharset()); + ByteBuf byteBuf = UnpooledByteBufAllocator.DEFAULT.buffer(bytes.length + EXTRA_SPACE); + byteBuf.writeBytes(HEADER); + byteBuf.writeBytes(bytes); + byteBuf.writeBytes(FOOTER); + response.writeAndFlush((O) byteBuf); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/test/java/com/netflix/hystrix/HystrixCommandMetricsSamples.java b/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/test/java/com/netflix/hystrix/HystrixCommandMetricsSamples.java new file mode 100644 index 0000000..0c0e621 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/test/java/com/netflix/hystrix/HystrixCommandMetricsSamples.java @@ -0,0 +1,63 @@ +/** + * 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 com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifierDefault; + +/** + * Not very elegant, but there is no other way to create this data directly for testing + * purposes, as {@link com.netflix.hystrix.HystrixCommandMetrics} has no public constructors, + * only package private. + * + * @author Tomasz Bak + */ +public class HystrixCommandMetricsSamples { + + public static final HystrixCommandMetrics SAMPLE_1; + + private static class MyHystrixCommandKey implements HystrixCommandKey { + @Override + public String name() { + return "hystrixKey"; + } + } + + private static class MyHystrixCommandGroupKey implements HystrixCommandGroupKey { + @Override + public String name() { + return "hystrixCommandGroupKey"; + } + } + + private static class MyHystrixThreadPoolKey implements HystrixThreadPoolKey { + @Override + public String name() { + return "hystrixThreadPoolKey"; + } + } + + private static class MyHystrixCommandProperties extends HystrixCommandProperties { + protected MyHystrixCommandProperties(HystrixCommandKey key) { + super(key); + } + } + + static { + HystrixCommandKey key = new MyHystrixCommandKey(); + SAMPLE_1 = new HystrixCommandMetrics(key, new MyHystrixCommandGroupKey(), new MyHystrixThreadPoolKey(), + new MyHystrixCommandProperties(key), HystrixEventNotifierDefault.getInstance()); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/test/java/com/netflix/hystrix/contrib/rxnetty/metricsstream/HystrixMetricsStreamHandlerTest.java b/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/test/java/com/netflix/hystrix/contrib/rxnetty/metricsstream/HystrixMetricsStreamHandlerTest.java new file mode 100644 index 0000000..e83d8bd --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-rx-netty-metrics-stream/src/test/java/com/netflix/hystrix/contrib/rxnetty/metricsstream/HystrixMetricsStreamHandlerTest.java @@ -0,0 +1,134 @@ +/** + * 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.contrib.rxnetty.metricsstream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandMetricsSamples; +import io.netty.buffer.ByteBuf; +import io.reactivex.netty.RxNetty; +import io.reactivex.netty.pipeline.PipelineConfigurators; +import io.reactivex.netty.protocol.http.client.HttpClient; +import io.reactivex.netty.protocol.http.client.HttpClientRequest; +import io.reactivex.netty.protocol.http.client.HttpClientResponse; +import io.reactivex.netty.protocol.http.server.HttpServer; +import io.reactivex.netty.protocol.http.server.HttpServerRequest; +import io.reactivex.netty.protocol.http.server.HttpServerResponse; +import io.reactivex.netty.protocol.http.server.RequestHandler; +import io.reactivex.netty.protocol.http.sse.ServerSentEvent; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import rx.Observable; +import rx.functions.Func1; + +import java.util.Collection; +import java.util.Collections; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import static com.netflix.hystrix.contrib.rxnetty.metricsstream.HystrixMetricsStreamHandler.*; +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; +import static org.powermock.api.easymock.PowerMock.*; + +/** + * @author Tomasz Bak + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest(HystrixCommandMetrics.class) +public class HystrixMetricsStreamHandlerTest { + + private static final ObjectMapper mapper = new ObjectMapper(); + + private static Collection SAMPLE_HYSTRIX_COMMAND_METRICS = + Collections.singleton(HystrixCommandMetricsSamples.SAMPLE_1); + + private int port; + private HttpServer server; + private HttpClient client; + + @Before + public void setUp() throws Exception { + server = createServer(); + + client = RxNetty.newHttpClientBuilder("localhost", port) + .withNoConnectionPooling() + .pipelineConfigurator(PipelineConfigurators.clientSseConfigurator()) + .build(); + + mockStatic(HystrixCommandMetrics.class); + expect(HystrixCommandMetrics.getInstances()).andReturn(SAMPLE_HYSTRIX_COMMAND_METRICS).anyTimes(); + } + + @After + public void tearDown() throws Exception { + if (server != null) { + server.shutdown(); + } + if (client != null) { + client.shutdown(); + } + } + + @Test + public void testMetricsAreDeliveredAsSseStream() throws Exception { + replayAll(); + + Observable objectObservable = client.submit(HttpClientRequest.createGet(DEFAULT_HYSTRIX_PREFIX)) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(HttpClientResponse httpClientResponse) { + return httpClientResponse.getContent().take(1); + } + }); + + Object first = Observable.amb(objectObservable, Observable.timer(5000, TimeUnit.MILLISECONDS)).toBlocking().first(); + + assertTrue("Expected SSE message", first instanceof ServerSentEvent); + ServerSentEvent sse = (ServerSentEvent) first; + JsonNode jsonNode = mapper.readTree(sse.contentAsString()); + assertEquals("Expected hystrix key name", HystrixCommandMetricsSamples.SAMPLE_1.getCommandKey().name(), jsonNode.get("name").asText()); + } + + // We try a few times in case we hit into used port. + private HttpServer createServer() { + Random random = new Random(); + Exception error = null; + for (int i = 0; i < 3 && server == null; i++) { + port = 10000 + random.nextInt(50000); + try { + return RxNetty.newHttpServerBuilder(port, new HystrixMetricsStreamHandler( + DEFAULT_HYSTRIX_PREFIX, + DEFAULT_INTERVAL, + new RequestHandler() { // Application handler + @Override + public Observable handle(HttpServerRequest request, HttpServerResponse response) { + return Observable.empty(); + } + } + )).build().start(); + } catch (Exception e) { + error = e; + } + } + throw new RuntimeException("Cannot initialize RxNetty server", error); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/README.md b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/README.md new file mode 100644 index 0000000..8a415c3 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/README.md @@ -0,0 +1,48 @@ +# hystrix-servo-metrics-publisher + +This is an implementation of [HystrixMetricsPublisher](http://netflix.github.com/Hystrix/javadoc/index.html?com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisher.html) that publishes metrics using [Netflix Servo](https://github.com/Netflix/servo). + +Servo enables metrics to be exposed via JMX or published to Graphite or CloudWatch. + +See the [Metrics & Monitoring](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring) Wiki for more information. + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-servo-metrics-publisher%22). + +Example for Maven: + +```xml + + com.netflix.hystrix + hystrix-servo-metrics-publisher + 1.1.2 + +``` + +and for Ivy: + +```xml + +``` + +#Example Publishing metrics to Graphite + +Some minimal Servo configuration must be done during application startup (ie time of Hystrix plugin registration) to enable Servo to publish Hystrix metrics to Graphite: + +```java +// Registry plugin with hystrix +HystrixPlugins.getInstance().registerMetricsPublisher(HystrixServoMetricsPublisher.getInstance()); + + ........ + +// Minimal Servo configuration for publishing to Graphite +final List observers = new ArrayList(); + +observers.add(new GraphiteMetricObserver(getHostName(), "graphite-server.example.com:2003")); +PollScheduler.getInstance().start(); +PollRunnable task = new PollRunnable(new MonitorRegistryMetricPoller(), BasicMetricFilter.MATCH_ALL, true, observers); +PollScheduler.getInstance().addPoller(task, 5, TimeUnit.SECONDS); +``` + +It's that simple. See [Servo wiki](https://github.com/Netflix/servo/wiki/Getting-Started) and [Publishing to Graphite](https://github.com/Netflix/servo/wiki/Publishing-to-Graphite) for full documentation. diff --git a/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/build.gradle new file mode 100644 index 0000000..c3f61d7 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/build.gradle @@ -0,0 +1,6 @@ +dependencies { + compileApi project(':hystrix-core') + compileApi 'com.netflix.servo:servo-core:0.10.1' + testCompile 'junit:junit-dep:4.10' + testCompile 'org.mockito:mockito-all:1.9.5' +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisher.java b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisher.java new file mode 100644 index 0000000..a8b76ec --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisher.java @@ -0,0 +1,76 @@ +/** + * Copyright 2012 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.contrib.servopublisher; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCollapser; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCommand; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherThreadPool; + +/** + * Servo implementation of {@link HystrixMetricsPublisher}. + *

+ * See Wiki docs about plugins for more information. + */ +public class HystrixServoMetricsPublisher extends HystrixMetricsPublisher { + + private static HystrixServoMetricsPublisher INSTANCE = null; + + public static HystrixServoMetricsPublisher getInstance() { + if (INSTANCE == null) { + HystrixServoMetricsPublisher temp = createInstance(); + INSTANCE = temp; + } + return INSTANCE; + } + + private static synchronized HystrixServoMetricsPublisher createInstance() { + if (INSTANCE == null) { + return new HystrixServoMetricsPublisher(); + } else { + return INSTANCE; + } + } + + private HystrixServoMetricsPublisher() { + } + + @Override + public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + return new HystrixServoMetricsPublisherCommand(commandKey, commandGroupKey, metrics, circuitBreaker, properties); + } + + @Override + public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + return new HystrixServoMetricsPublisherThreadPool(threadPoolKey, metrics, properties); + } + + @Override + public HystrixMetricsPublisherCollapser getMetricsPublisherForCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { + return new HystrixServoMetricsPublisherCollapser(collapserKey, metrics, properties); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherAbstract.java b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherAbstract.java new file mode 100644 index 0000000..e846226 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherAbstract.java @@ -0,0 +1,109 @@ +/** + * Copyright 2012 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.contrib.servopublisher; + +import com.netflix.hystrix.HystrixMetrics; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import com.netflix.servo.annotations.DataSourceLevel; +import com.netflix.servo.annotations.DataSourceType; +import com.netflix.servo.monitor.AbstractMonitor; +import com.netflix.servo.monitor.Counter; +import com.netflix.servo.monitor.Gauge; +import com.netflix.servo.monitor.Monitor; +import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.servo.tag.Tag; + +/** + * Utility used for Servo (https://github.com/Netflix/servo) based implementations of metrics publishers. + */ +/* package */abstract class HystrixServoMetricsPublisherAbstract { + + protected abstract Tag getServoTypeTag(); + + protected abstract Tag getServoInstanceTag(); + + protected abstract class InformationalMetric extends AbstractMonitor { + public InformationalMetric(MonitorConfig config) { + super(config.withAdditionalTag(DataSourceType.INFORMATIONAL).withAdditionalTag(getServoTypeTag()).withAdditionalTag(getServoInstanceTag())); + } + + @Override + public K getValue(int n) { + return getValue(); + } + + @Override + public abstract K getValue(); + } + + protected abstract class CounterMetric extends AbstractMonitor implements Counter { + public CounterMetric(MonitorConfig config) { + super(config.withAdditionalTag(DataSourceType.COUNTER).withAdditionalTag(getServoTypeTag()).withAdditionalTag(getServoInstanceTag())); + } + + @Override + public Number getValue(int n) { + return getValue(); + } + + @Override + public abstract Number getValue(); + + @Override + public void increment() { + throw new IllegalStateException("We are wrapping a value instead."); + } + + @Override + public void increment(long arg0) { + throw new IllegalStateException("We are wrapping a value instead."); + } + } + + protected abstract class GaugeMetric extends AbstractMonitor implements Gauge { + public GaugeMetric(MonitorConfig config) { + super(config.withAdditionalTag(DataSourceType.GAUGE).withAdditionalTag(getServoTypeTag()).withAdditionalTag(getServoInstanceTag())); + } + + @Override + public Number getValue(int n) { + return getValue(); + } + + @Override + public abstract Number getValue(); + } + + protected Monitor getCumulativeCountForEvent(String name, final HystrixMetrics metrics, final HystrixRollingNumberEvent event) { + return new CounterMetric(MonitorConfig.builder(name).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + return metrics.getCumulativeCount(event); + } + + }; + } + + protected Monitor getRollingCountForEvent(String name, final HystrixMetrics metrics, final HystrixRollingNumberEvent event) { + return new GaugeMetric(MonitorConfig.builder(name).withTag(DataSourceLevel.DEBUG).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Number getValue() { + return metrics.getRollingCount(event); + } + + }; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCollapser.java b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCollapser.java new file mode 100644 index 0000000..97b4030 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCollapser.java @@ -0,0 +1,300 @@ +/** + * Copyright 2012 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.contrib.servopublisher; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.consumer.CumulativeCollapserEventCounterStream; +import com.netflix.hystrix.metric.consumer.RollingCollapserBatchSizeDistributionStream; +import com.netflix.hystrix.metric.consumer.RollingCollapserEventCounterStream; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCollapser; +import com.netflix.servo.DefaultMonitorRegistry; +import com.netflix.servo.annotations.DataSourceLevel; +import com.netflix.servo.monitor.BasicCompositeMonitor; +import com.netflix.servo.monitor.Monitor; +import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.servo.tag.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link HystrixMetricsPublisherCollapser} using Servo (https://github.com/Netflix/servo) + */ +public class HystrixServoMetricsPublisherCollapser extends HystrixServoMetricsPublisherAbstract implements HystrixMetricsPublisherCollapser { + + private static final Logger logger = LoggerFactory.getLogger(HystrixServoMetricsPublisherCollapser.class); + + private final HystrixCollapserKey key; + private final HystrixCollapserMetrics metrics; + private final HystrixCollapserProperties properties; + private final Tag servoInstanceTag; + private final Tag servoTypeTag; + + public HystrixServoMetricsPublisherCollapser(HystrixCollapserKey threadPoolKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { + this.key = threadPoolKey; + this.metrics = metrics; + this.properties = properties; + + this.servoInstanceTag = new Tag() { + + @Override + public String getKey() { + return "instance"; + } + + @Override + public String getValue() { + return key.name(); + } + + @Override + public String tagString() { + return key.name(); + } + + }; + this.servoTypeTag = new Tag() { + + @Override + public String getKey() { + return "type"; + } + + @Override + public String getValue() { + return "HystrixCollapser"; + } + + @Override + public String tagString() { + return "HystrixCollapser"; + } + + }; + } + + @Override + public void initialize() { + /* list of monitors */ + List> monitors = getServoMonitors(); + + // publish metrics together under a single composite (it seems this name is ignored) + MonitorConfig commandMetricsConfig = MonitorConfig.builder("HystrixCollapser_" + key.name()).build(); + BasicCompositeMonitor commandMetricsMonitor = new BasicCompositeMonitor(commandMetricsConfig, monitors); + + DefaultMonitorRegistry.getInstance().register(commandMetricsMonitor); + RollingCollapserBatchSizeDistributionStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + RollingCollapserEventCounterStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + CumulativeCollapserEventCounterStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + } + + @Override + protected Tag getServoTypeTag() { + return servoTypeTag; + } + + @Override + protected Tag getServoInstanceTag() { + return servoInstanceTag; + } + + protected Monitor getCumulativeMonitor(final String name, final HystrixEventType.Collapser event) { + return new CounterMetric(MonitorConfig.builder(name).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + return metrics.getCumulativeCount(event); + } + }; + } + + protected Monitor safelyGetCumulativeMonitor(final String name, final Func0 eventThunk) { + return new CounterMetric(MonitorConfig.builder(name).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + try { + return metrics.getCumulativeCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing Servo metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }; + } + + protected Monitor getRollingMonitor(final String name, final HystrixEventType.Collapser event) { + return new GaugeMetric(MonitorConfig.builder(name).withTag(DataSourceLevel.DEBUG).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + return metrics.getRollingCount(event); + } + }; + } + + protected Monitor safelyGetRollingMonitor(final String name, final Func0 eventThunk) { + return new GaugeMetric(MonitorConfig.builder(name).withTag(DataSourceLevel.DEBUG).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + try { + return metrics.getRollingCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing Servo metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }; + } + + protected Monitor getBatchSizeMeanMonitor(final String name) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metrics.getBatchSizeMean(); + } + }; + } + + protected Monitor getBatchSizePercentileMonitor(final String name, final double percentile) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metrics.getBatchSizePercentile(percentile); + } + }; + } + + protected Monitor getShardSizeMeanMonitor(final String name) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metrics.getShardSizeMean(); + } + }; + } + + protected Monitor getShardSizePercentileMonitor(final String name, final double percentile) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metrics.getShardSizePercentile(percentile); + } + }; + } + + /** + * Servo will flatten metric names as: getServoTypeTag()_getServoInstanceTag()_monitorName + * + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-servo-metric-publisher, + * the code below may reference a HystrixEventType.Collapser that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + */ + private List> getServoMonitors() { + + List> monitors = new ArrayList>(); + + monitors.add(new InformationalMetric(MonitorConfig.builder("name").build()) { + @Override + public String getValue() { + return key.name(); + } + }); + + // allow Servo and monitor to know exactly at what point in time these stats are for so they can be plotted accurately + monitors.add(new GaugeMetric(MonitorConfig.builder("currentTime").withTag(DataSourceLevel.DEBUG).build()) { + @Override + public Number getValue() { + return System.currentTimeMillis(); + } + }); + + //collapser event cumulative metrics + monitors.add(safelyGetCumulativeMonitor("countRequestsBatched", new Func0() { + @Override + public HystrixEventType.Collapser call() { + return HystrixEventType.Collapser.ADDED_TO_BATCH; + } + })); + monitors.add(safelyGetCumulativeMonitor("countBatches", new Func0() { + @Override + public HystrixEventType.Collapser call() { + return HystrixEventType.Collapser.BATCH_EXECUTED; + } + })); + monitors.add(safelyGetCumulativeMonitor("countResponsesFromCache", new Func0() { + @Override + public HystrixEventType.Collapser call() { + return HystrixEventType.Collapser.RESPONSE_FROM_CACHE; + } + })); + + //batch size distribution metrics + monitors.add(getBatchSizeMeanMonitor("batchSize_mean")); + monitors.add(getBatchSizePercentileMonitor("batchSize_percentile_25", 25)); + monitors.add(getBatchSizePercentileMonitor("batchSize_percentile_50", 50)); + monitors.add(getBatchSizePercentileMonitor("batchSize_percentile_75", 75)); + monitors.add(getBatchSizePercentileMonitor("batchSize_percentile_95", 95)); + monitors.add(getBatchSizePercentileMonitor("batchSize_percentile_99", 99)); + monitors.add(getBatchSizePercentileMonitor("batchSize_percentile_99_5", 99.5)); + monitors.add(getBatchSizePercentileMonitor("batchSize_percentile_100", 100)); + + //shard size distribution metrics + monitors.add(getShardSizeMeanMonitor("shardSize_mean")); + monitors.add(getShardSizePercentileMonitor("shardSize_percentile_25", 25)); + monitors.add(getShardSizePercentileMonitor("shardSize_percentile_50", 50)); + monitors.add(getShardSizePercentileMonitor("shardSize_percentile_75", 75)); + monitors.add(getShardSizePercentileMonitor("shardSize_percentile_95", 95)); + monitors.add(getShardSizePercentileMonitor("shardSize_percentile_99", 99)); + monitors.add(getShardSizePercentileMonitor("shardSize_percentile_99_5", 99.5)); + monitors.add(getShardSizePercentileMonitor("shardSize_percentile_100", 100)); + + // properties (so the values can be inspected and monitored) + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_rollingStatisticalWindowInMilliseconds").build()) { + @Override + public Number getValue() { + return properties.metricsRollingStatisticalWindowInMilliseconds().get(); + } + }); + + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_requestCacheEnabled").build()) { + @Override + public Boolean getValue() { + return properties.requestCacheEnabled().get(); + } + }); + + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_maxRequestsInBatch").build()) { + @Override + public Number getValue() { + return properties.maxRequestsInBatch().get(); + } + }); + + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_timerDelayInMilliseconds").build()) { + @Override + public Number getValue() { + return properties.timerDelayInMilliseconds().get(); + } + }); + + return monitors; + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCommand.java b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCommand.java new file mode 100644 index 0000000..0d9b342 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCommand.java @@ -0,0 +1,617 @@ +/** + * Copyright 2012 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.contrib.servopublisher; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.consumer.CumulativeCommandEventCounterStream; +import com.netflix.hystrix.metric.consumer.RollingCommandEventCounterStream; +import com.netflix.hystrix.metric.consumer.RollingCommandLatencyDistributionStream; +import com.netflix.hystrix.metric.consumer.RollingCommandMaxConcurrencyStream; +import com.netflix.hystrix.metric.consumer.RollingCommandUserLatencyDistributionStream; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCommand; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import com.netflix.servo.DefaultMonitorRegistry; +import com.netflix.servo.annotations.DataSourceLevel; +import com.netflix.servo.monitor.BasicCompositeMonitor; +import com.netflix.servo.monitor.Monitor; +import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.servo.tag.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +import java.util.ArrayList; +import java.util.List; + +/** + * Concrete Implementation of {@link HystrixMetricsPublisherCommand} using Servo (https://github.com/Netflix/servo) + * + * This class should encapsulate all logic around how to pull metrics. This will allow any other custom Servo publisher + * to extend. Then, if that class wishes to override {@link #initialize()}, that concrete implementation can choose + * by picking the set of semantic metrics and names, rather than providing an implementation of how. + */ +public class HystrixServoMetricsPublisherCommand extends HystrixServoMetricsPublisherAbstract implements HystrixMetricsPublisherCommand { + + private static final Logger logger = LoggerFactory.getLogger(HystrixServoMetricsPublisherCommand.class); + + private final HystrixCommandKey key; + private final HystrixCommandGroupKey commandGroupKey; + private final HystrixCommandMetrics metrics; + private final HystrixCircuitBreaker circuitBreaker; + private final HystrixCommandProperties properties; + private final Tag servoInstanceTag; + private final Tag servoTypeTag; + + public HystrixServoMetricsPublisherCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + this.key = commandKey; + this.commandGroupKey = commandGroupKey; + this.metrics = metrics; + this.circuitBreaker = circuitBreaker; + this.properties = properties; + this.servoInstanceTag = new Tag() { + + @Override + public String getKey() { + return "instance"; + } + + @Override + public String getValue() { + return key.name(); + } + + @Override + public String tagString() { + return key.name(); + } + + }; + this.servoTypeTag = new Tag() { + + @Override + public String getKey() { + return "type"; + } + + @Override + public String getValue() { + return "HystrixCommand"; + } + + @Override + public String tagString() { + return "HystrixCommand"; + } + + }; + } + + @Override + public void initialize() { + /* list of monitors */ + List> monitors = getServoMonitors(); + + // publish metrics together under a single composite (it seems this name is ignored) + MonitorConfig commandMetricsConfig = MonitorConfig.builder("HystrixCommand_" + key.name()).build(); + BasicCompositeMonitor commandMetricsMonitor = new BasicCompositeMonitor(commandMetricsConfig, monitors); + + DefaultMonitorRegistry.getInstance().register(commandMetricsMonitor); + RollingCommandEventCounterStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + CumulativeCommandEventCounterStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + RollingCommandLatencyDistributionStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + RollingCommandUserLatencyDistributionStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + RollingCommandMaxConcurrencyStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + } + + @Override + protected Tag getServoTypeTag() { + return servoTypeTag; + } + + @Override + protected Tag getServoInstanceTag() { + return servoInstanceTag; + } + + protected final Func0 currentConcurrentExecutionCountThunk = new Func0() { + @Override + public Integer call() { + return metrics.getCurrentConcurrentExecutionCount(); + } + }; + + protected final Func0 rollingMaxConcurrentExecutionCountThunk = new Func0() { + @Override + public Long call() { + return metrics.getRollingMaxConcurrentExecutions(); + } + }; + + protected final Func0 errorPercentageThunk = new Func0() { + @Override + public Integer call() { + return metrics.getHealthCounts().getErrorPercentage(); + } + }; + + protected final Func0 currentTimeThunk = new Func0() { + @Override + public Number call() { + return System.currentTimeMillis(); + } + }; + + /** + * Convert from HystrixEventType to HystrixRollingNumberEvent + * @param eventType HystrixEventType + * @return HystrixRollingNumberEvent + * @deprecated Instead, use {@link HystrixRollingNumberEvent#from(HystrixEventType)} + */ + @Deprecated + protected final HystrixRollingNumberEvent getRollingNumberTypeFromEventType(HystrixEventType eventType) { + return HystrixRollingNumberEvent.from(eventType); + } + + protected Monitor getCumulativeMonitor(final String name, final HystrixEventType event) { + return new CounterMetric(MonitorConfig.builder(name).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + return metrics.getCumulativeCount(event); + } + }; + } + + protected Monitor safelyGetCumulativeMonitor(final String name, final Func0 eventThunk) { + return new CounterMetric(MonitorConfig.builder(name).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + try { + HystrixEventType eventType = eventThunk.call(); + return metrics.getCumulativeCount(HystrixRollingNumberEvent.from(eventType)); + } catch (NoSuchFieldError error) { + logger.error("While publishing Servo metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }; + } + + protected Monitor getRollingMonitor(final String name, final HystrixEventType event) { + return new GaugeMetric(MonitorConfig.builder(name).withTag(DataSourceLevel.DEBUG).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + return metrics.getRollingCount(event); + } + }; + } + + protected Monitor safelyGetRollingMonitor(final String name, final Func0 eventThunk) { + return new GaugeMetric(MonitorConfig.builder(name).withTag(DataSourceLevel.DEBUG).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + try { + HystrixEventType eventType = eventThunk.call(); + return metrics.getRollingCount(HystrixRollingNumberEvent.from(eventType)); + } catch (NoSuchFieldError error) { + logger.error("While publishing Servo metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }; + } + + protected Monitor getExecutionLatencyMeanMonitor(final String name) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metrics.getExecutionTimeMean(); + } + }; + } + + protected Monitor getExecutionLatencyPercentileMonitor(final String name, final double percentile) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metrics.getExecutionTimePercentile(percentile); + } + }; + } + + protected Monitor getTotalLatencyMeanMonitor(final String name) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metrics.getTotalTimeMean(); + } + }; + } + + protected Monitor getTotalLatencyPercentileMonitor(final String name, final double percentile) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metrics.getTotalTimePercentile(percentile); + } + }; + } + + protected Monitor getCurrentValueMonitor(final String name, final Func0 metricToEvaluate) { + return new GaugeMetric(MonitorConfig.builder(name).build()) { + @Override + public Number getValue() { + return metricToEvaluate.call(); + } + }; + } + + protected Monitor getCurrentValueMonitor(final String name, final Func0 metricToEvaluate, final Tag tag) { + return new GaugeMetric(MonitorConfig.builder(name).withTag(tag).build()) { + @Override + public Number getValue() { + return metricToEvaluate.call(); + } + }; + } + + /** + * Servo will flatten metric names as: getServoTypeTag()_getServoInstanceTag()_monitorName + * + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-servo-metric-publisher, + * the code below may reference a HystrixEventType that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + * + */ + private List> getServoMonitors() { + + List> monitors = new ArrayList>(); + + monitors.add(new InformationalMetric(MonitorConfig.builder("isCircuitBreakerOpen").build()) { + @Override + public Boolean getValue() { + return circuitBreaker.isOpen(); + } + }); + + // allow Servo and monitor to know exactly at what point in time these stats are for so they can be plotted accurately + monitors.add(getCurrentValueMonitor("currentTime", currentTimeThunk, DataSourceLevel.DEBUG)); + + // cumulative counts + monitors.add(safelyGetCumulativeMonitor("countBadRequests", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.BAD_REQUEST; + } + })); + monitors.add(safelyGetCumulativeMonitor("countCollapsedRequests", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.COLLAPSED; + } + })); + monitors.add(safelyGetCumulativeMonitor("countEmit", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.EMIT; + } + })); + monitors.add(safelyGetCumulativeMonitor("countExceptionsThrown", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.EXCEPTION_THROWN; + } + })); + monitors.add(safelyGetCumulativeMonitor("countFailure", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FAILURE; + } + })); + monitors.add(safelyGetCumulativeMonitor("countFallbackEmit", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_EMIT; + } + })); + monitors.add(safelyGetCumulativeMonitor("countFallbackFailure", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_FAILURE; + } + })); + monitors.add(safelyGetCumulativeMonitor("countFallbackMissing", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_MISSING; + } + })); + monitors.add(safelyGetCumulativeMonitor("countFallbackRejection", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_REJECTION; + } + })); + monitors.add(safelyGetCumulativeMonitor("countFallbackSuccess", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_SUCCESS; + } + })); + monitors.add(safelyGetCumulativeMonitor("countResponsesFromCache", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.RESPONSE_FROM_CACHE; + } + })); + monitors.add(safelyGetCumulativeMonitor("countSemaphoreRejected", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SEMAPHORE_REJECTED; + } + })); + monitors.add(safelyGetCumulativeMonitor("countShortCircuited", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SHORT_CIRCUITED; + } + })); + monitors.add(safelyGetCumulativeMonitor("countSuccess", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SUCCESS; + } + })); + monitors.add(safelyGetCumulativeMonitor("countThreadPoolRejected", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.THREAD_POOL_REJECTED; + } + })); + monitors.add(safelyGetCumulativeMonitor("countTimeout", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.TIMEOUT; + } + })); + + // rolling counts + monitors.add(safelyGetRollingMonitor("rollingCountBadRequests", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.BAD_REQUEST; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountCollapsedRequests", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.COLLAPSED; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountEmit", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.EMIT; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountExceptionsThrown", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.EXCEPTION_THROWN; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountFailure", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FAILURE; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountFallbackEmit", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_EMIT; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountFallbackFailure", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_FAILURE; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountFallbackMissing", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_MISSING; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountFallbackRejection", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_REJECTION; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountFallbackSuccess", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_SUCCESS; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountResponsesFromCache", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.RESPONSE_FROM_CACHE; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountSemaphoreRejected", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SEMAPHORE_REJECTED; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountShortCircuited", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SHORT_CIRCUITED; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountSuccess", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SUCCESS; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountThreadPoolRejected", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.THREAD_POOL_REJECTED; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountTimeout", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.TIMEOUT; + } + })); + + // the number of executionSemaphorePermits in use right now + monitors.add(getCurrentValueMonitor("executionSemaphorePermitsInUse", currentConcurrentExecutionCountThunk)); + + // error percentage derived from current metrics + monitors.add(getCurrentValueMonitor("errorPercentage", errorPercentageThunk)); + + // execution latency metrics + monitors.add(getExecutionLatencyMeanMonitor("latencyExecute_mean")); + monitors.add(getExecutionLatencyPercentileMonitor("latencyExecute_percentile_5", 5)); + monitors.add(getExecutionLatencyPercentileMonitor("latencyExecute_percentile_25", 25)); + monitors.add(getExecutionLatencyPercentileMonitor("latencyExecute_percentile_50", 50)); + monitors.add(getExecutionLatencyPercentileMonitor("latencyExecute_percentile_75", 75)); + monitors.add(getExecutionLatencyPercentileMonitor("latencyExecute_percentile_90", 90)); + monitors.add(getExecutionLatencyPercentileMonitor("latencyExecute_percentile_99", 99)); + monitors.add(getExecutionLatencyPercentileMonitor("latencyExecute_percentile_995", 99.5)); + + // total latency metrics + monitors.add(getTotalLatencyMeanMonitor("latencyTotal_mean")); + monitors.add(getTotalLatencyPercentileMonitor("latencyTotal_percentile_5", 5)); + monitors.add(getTotalLatencyPercentileMonitor("latencyTotal_percentile_25", 25)); + monitors.add(getTotalLatencyPercentileMonitor("latencyTotal_percentile_50", 50)); + monitors.add(getTotalLatencyPercentileMonitor("latencyTotal_percentile_75", 75)); + monitors.add(getTotalLatencyPercentileMonitor("latencyTotal_percentile_90", 90)); + monitors.add(getTotalLatencyPercentileMonitor("latencyTotal_percentile_99", 99)); + monitors.add(getTotalLatencyPercentileMonitor("latencyTotal_percentile_995", 99.5)); + + // group + monitors.add(new InformationalMetric(MonitorConfig.builder("commandGroup").build()) { + @Override + public String getValue() { + return commandGroupKey != null ? commandGroupKey.name() : null; + } + }); + + // properties (so the values can be inspected and monitored) + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_rollingStatisticalWindowInMilliseconds").build()) { + @Override + public Number getValue() { + return properties.metricsRollingStatisticalWindowInMilliseconds().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_circuitBreakerRequestVolumeThreshold").build()) { + @Override + public Number getValue() { + return properties.circuitBreakerRequestVolumeThreshold().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_circuitBreakerSleepWindowInMilliseconds").build()) { + @Override + public Number getValue() { + return properties.circuitBreakerSleepWindowInMilliseconds().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_circuitBreakerErrorThresholdPercentage").build()) { + @Override + public Number getValue() { + return properties.circuitBreakerErrorThresholdPercentage().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_circuitBreakerForceOpen").build()) { + @Override + public Boolean getValue() { + return properties.circuitBreakerForceOpen().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_circuitBreakerForceClosed").build()) { + @Override + public Boolean getValue() { + return properties.circuitBreakerForceClosed().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_executionIsolationThreadTimeoutInMilliseconds").build()) { + @Override + public Number getValue() { + return properties.executionTimeoutInMilliseconds().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_executionTimeoutInMilliseconds").build()) { + @Override + public Number getValue() { + return properties.executionTimeoutInMilliseconds().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_executionIsolationStrategy").build()) { + @Override + public String getValue() { + return properties.executionIsolationStrategy().get().name(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_metricsRollingPercentileEnabled").build()) { + @Override + public Boolean getValue() { + return properties.metricsRollingPercentileEnabled().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_requestCacheEnabled").build()) { + @Override + public Boolean getValue() { + return properties.requestCacheEnabled().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_requestLogEnabled").build()) { + @Override + public Boolean getValue() { + return properties.requestLogEnabled().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_executionIsolationSemaphoreMaxConcurrentRequests").build()) { + @Override + public Number getValue() { + return properties.executionIsolationSemaphoreMaxConcurrentRequests().get(); + } + }); + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests").build()) { + @Override + public Number getValue() { + return properties.fallbackIsolationSemaphoreMaxConcurrentRequests().get(); + } + }); + + return monitors; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherThreadPool.java b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherThreadPool.java new file mode 100644 index 0000000..bc8ed09 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherThreadPool.java @@ -0,0 +1,290 @@ +/** + * Copyright 2012 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.contrib.servopublisher; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.metric.consumer.CumulativeThreadPoolEventCounterStream; +import com.netflix.hystrix.metric.consumer.RollingThreadPoolMaxConcurrencyStream; +import com.netflix.hystrix.metric.consumer.RollingThreadPoolEventCounterStream; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherThreadPool; +import com.netflix.servo.DefaultMonitorRegistry; +import com.netflix.servo.annotations.DataSourceLevel; +import com.netflix.servo.monitor.BasicCompositeMonitor; +import com.netflix.servo.monitor.Monitor; +import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.servo.tag.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link HystrixMetricsPublisherThreadPool} using Servo (https://github.com/Netflix/servo) + */ +public class HystrixServoMetricsPublisherThreadPool extends HystrixServoMetricsPublisherAbstract implements HystrixMetricsPublisherThreadPool { + + private static final Logger logger = LoggerFactory.getLogger(HystrixServoMetricsPublisherThreadPool.class); + + private final HystrixThreadPoolKey key; + private final HystrixThreadPoolMetrics metrics; + private final HystrixThreadPoolProperties properties; + private final Tag servoInstanceTag; + private final Tag servoTypeTag; + + public HystrixServoMetricsPublisherThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + this.key = threadPoolKey; + this.metrics = metrics; + this.properties = properties; + + this.servoInstanceTag = new Tag() { + + @Override + public String getKey() { + return "instance"; + } + + @Override + public String getValue() { + return key.name(); + } + + @Override + public String tagString() { + return key.name(); + } + + }; + this.servoTypeTag = new Tag() { + + @Override + public String getKey() { + return "type"; + } + + @Override + public String getValue() { + return "HystrixThreadPool"; + } + + @Override + public String tagString() { + return "HystrixThreadPool"; + } + + }; + } + + @Override + public void initialize() { + /* list of monitors */ + List> monitors = getServoMonitors(); + + // publish metrics together under a single composite (it seems this name is ignored) + MonitorConfig commandMetricsConfig = MonitorConfig.builder("HystrixThreadPool_" + key.name()).build(); + BasicCompositeMonitor commandMetricsMonitor = new BasicCompositeMonitor(commandMetricsConfig, monitors); + + DefaultMonitorRegistry.getInstance().register(commandMetricsMonitor); + RollingThreadPoolEventCounterStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + CumulativeThreadPoolEventCounterStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + RollingThreadPoolMaxConcurrencyStream.getInstance(key, properties).startCachingStreamValuesIfUnstarted(); + } + + @Override + protected Tag getServoTypeTag() { + return servoTypeTag; + } + + @Override + protected Tag getServoInstanceTag() { + return servoInstanceTag; + } + + protected Monitor safelyGetCumulativeMonitor(final String name, final Func0 eventThunk) { + return new CounterMetric(MonitorConfig.builder(name).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + try { + return metrics.getCumulativeCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing Servo metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }; + } + + + protected Monitor safelyGetRollingMonitor(final String name, final Func0 eventThunk) { + return new GaugeMetric(MonitorConfig.builder(name).withTag(DataSourceLevel.DEBUG).withTag(getServoTypeTag()).withTag(getServoInstanceTag()).build()) { + @Override + public Long getValue() { + try { + return metrics.getRollingCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing Servo metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }; + } + + /** + * Servo will flatten metric names as: getServoTypeTag()_getServoInstanceTag()_monitorName + * + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-servo-metric-publisher, + * the code below may reference a HystrixEventType.ThreadPool that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + */ + private List> getServoMonitors() { + + List> monitors = new ArrayList>(); + + monitors.add(new InformationalMetric(MonitorConfig.builder("name").build()) { + @Override + public String getValue() { + return key.name(); + } + }); + + // allow Servo and monitor to know exactly at what point in time these stats are for so they can be plotted accurately + monitors.add(new GaugeMetric(MonitorConfig.builder("currentTime").withTag(DataSourceLevel.DEBUG).build()) { + @Override + public Number getValue() { + return System.currentTimeMillis(); + } + }); + + monitors.add(new GaugeMetric(MonitorConfig.builder("threadActiveCount").build()) { + @Override + public Number getValue() { + return metrics.getCurrentActiveCount(); + } + }); + + monitors.add(new GaugeMetric(MonitorConfig.builder("completedTaskCount").build()) { + @Override + public Number getValue() { + return metrics.getCurrentCompletedTaskCount(); + } + }); + + monitors.add(new GaugeMetric(MonitorConfig.builder("largestPoolSize").build()) { + @Override + public Number getValue() { + return metrics.getCurrentLargestPoolSize(); + } + }); + + monitors.add(new GaugeMetric(MonitorConfig.builder("totalTaskCount").build()) { + @Override + public Number getValue() { + return metrics.getCurrentTaskCount(); + } + }); + + monitors.add(new GaugeMetric(MonitorConfig.builder("queueSize").build()) { + @Override + public Number getValue() { + return metrics.getCurrentQueueSize(); + } + }); + + monitors.add(new GaugeMetric(MonitorConfig.builder("rollingMaxActiveThreads").withTag(DataSourceLevel.DEBUG).build()) { + @Override + public Number getValue() { + return metrics.getRollingMaxActiveThreads(); + } + }); + + //thread pool event monitors + monitors.add(safelyGetCumulativeMonitor("countThreadsExecuted", new Func0() { + @Override + public HystrixEventType.ThreadPool call() { + return HystrixEventType.ThreadPool.EXECUTED; + } + })); + monitors.add(safelyGetCumulativeMonitor("countThreadsRejected", new Func0() { + @Override + public HystrixEventType.ThreadPool call() { + return HystrixEventType.ThreadPool.REJECTED; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountThreadsExecuted", new Func0() { + @Override + public HystrixEventType.ThreadPool call() { + return HystrixEventType.ThreadPool.EXECUTED; + } + })); + monitors.add(safelyGetRollingMonitor("rollingCountCommandsRejected", new Func0() { + @Override + public HystrixEventType.ThreadPool call() { + return HystrixEventType.ThreadPool.REJECTED; + } + })); + + // properties + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_corePoolSize").build()) { + @Override + public Number getValue() { + return properties.coreSize().get(); + } + }); + + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_maximumSize").build()) { + @Override + public Number getValue() { + return properties.maximumSize().get(); + } + }); + + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_actualMaximumSize").build()) { + @Override + public Number getValue() { + return properties.actualMaximumSize(); + } + }); + + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_keepAliveTimeInMinutes").build()) { + @Override + public Number getValue() { + return properties.keepAliveTimeMinutes().get(); + } + }); + + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_queueSizeRejectionThreshold").build()) { + @Override + public Number getValue() { + return properties.queueSizeRejectionThreshold().get(); + } + }); + + monitors.add(new InformationalMetric(MonitorConfig.builder("propertyValue_maxQueueSize").build()) { + @Override + public Number getValue() { + return properties.maxQueueSize().get(); + } + }); + + return monitors; + } + +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCommandTest.java b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCommandTest.java new file mode 100644 index 0000000..d50c48d --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-servo-metrics-publisher/src/test/java/com/netflix/hystrix/contrib/servopublisher/HystrixServoMetricsPublisherCommandTest.java @@ -0,0 +1,275 @@ +/** + * 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.contrib.servopublisher; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesCommandDefault; +import org.junit.Test; +import rx.Observable; +import rx.observers.TestSubscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class HystrixServoMetricsPublisherCommandTest { + + private static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ServoGROUP"); + private static HystrixCommandProperties.Setter propertiesSetter = HystrixCommandProperties.Setter() + .withCircuitBreakerEnabled(true) + .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) + .withExecutionTimeoutInMilliseconds(100) + .withMetricsRollingStatisticalWindowInMilliseconds(1000) + .withMetricsRollingPercentileWindowInMilliseconds(1000) + .withMetricsRollingPercentileWindowBuckets(10); + + + @Test + public void testCumulativeCounters() throws Exception { + //execute 10 commands/sec (8 SUCCESS, 1 FAILURE, 1 TIMEOUT). + //after 5 seconds, cumulative counters should have observed 50 commands (40 SUCCESS, 5 FAILURE, 5 TIMEOUT) + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("ServoCOMMAND-A"); + HystrixCircuitBreaker circuitBreaker = HystrixCircuitBreaker.Factory.getInstance(key); + HystrixCommandProperties properties = new HystrixPropertiesCommandDefault(key, propertiesSetter); + HystrixCommandMetrics metrics = HystrixCommandMetrics.getInstance(key, groupKey, properties); + HystrixServoMetricsPublisherCommand servoPublisher = new HystrixServoMetricsPublisherCommand(key, groupKey, metrics, circuitBreaker, properties); + servoPublisher.initialize(); + + final int NUM_SECONDS = 5; + + for (int i = 0; i < NUM_SECONDS; i++) { + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + Thread.sleep(10); + new TimeoutCommand(key).execute(); + new SuccessCommand(key).execute(); + new FailureCommand(key).execute(); + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + Thread.sleep(10); + new SuccessCommand(key).execute(); + } + + Thread.sleep(500); + + assertEquals(40L, servoPublisher.getCumulativeMonitor("success", HystrixEventType.SUCCESS).getValue()); + assertEquals(5L, servoPublisher.getCumulativeMonitor("timeout", HystrixEventType.TIMEOUT).getValue()); + assertEquals(5L, servoPublisher.getCumulativeMonitor("failure", HystrixEventType.FAILURE).getValue()); + assertEquals(10L, servoPublisher.getCumulativeMonitor("fallback_success", HystrixEventType.FALLBACK_SUCCESS).getValue()); + } + + @Test + public void testRollingCounters() throws Exception { + //execute 10 commands, then sleep for 2000ms to let these age out + //execute 10 commands again, these should show up in rolling count + + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("ServoCOMMAND-B"); + HystrixCircuitBreaker circuitBreaker = HystrixCircuitBreaker.Factory.getInstance(key); + HystrixCommandProperties properties = new HystrixPropertiesCommandDefault(key, propertiesSetter); + HystrixCommandMetrics metrics = HystrixCommandMetrics.getInstance(key, groupKey, properties); + HystrixServoMetricsPublisherCommand servoPublisher = new HystrixServoMetricsPublisherCommand(key, groupKey, metrics, circuitBreaker, properties); + servoPublisher.initialize(); + + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + new TimeoutCommand(key).execute(); + new SuccessCommand(key).execute(); + new FailureCommand(key).execute(); + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + + Thread.sleep(2000); + + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + new SuccessCommand(key).execute(); + new TimeoutCommand(key).execute(); + new SuccessCommand(key).execute(); + new FailureCommand(key).execute(); + new TimeoutCommand(key).execute(); + new TimeoutCommand(key).execute(); + new TimeoutCommand(key).execute(); + new TimeoutCommand(key).execute(); + + Thread.sleep(100); //time for 1 bucket roll + + assertEquals(4L, servoPublisher.getRollingMonitor("success", HystrixEventType.SUCCESS).getValue()); + assertEquals(5L, servoPublisher.getRollingMonitor("timeout", HystrixEventType.TIMEOUT).getValue()); + assertEquals(1L, servoPublisher.getRollingMonitor("failure", HystrixEventType.FAILURE).getValue()); + assertEquals(6L, servoPublisher.getRollingMonitor("falback_success", HystrixEventType.FALLBACK_SUCCESS).getValue()); + } + + @Test + public void testRollingLatencies() throws Exception { + //execute 10 commands, then sleep for 2000ms to let these age out + //execute 10 commands again, these should show up in rolling count + + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("ServoCOMMAND-C"); + HystrixCircuitBreaker circuitBreaker = HystrixCircuitBreaker.Factory.getInstance(key); + HystrixCommandProperties properties = new HystrixPropertiesCommandDefault(key, propertiesSetter); + HystrixCommandMetrics metrics = HystrixCommandMetrics.getInstance(key, groupKey, properties); + + HystrixServoMetricsPublisherCommand servoPublisher = new HystrixServoMetricsPublisherCommand(key, groupKey, metrics, circuitBreaker, properties); + servoPublisher.initialize(); + + new SuccessCommand(key, 5).execute(); + new SuccessCommand(key, 5).execute(); + new SuccessCommand(key, 5).execute(); + new TimeoutCommand(key).execute(); + new SuccessCommand(key, 5).execute(); + new FailureCommand(key, 5).execute(); + new SuccessCommand(key, 5).execute(); + new SuccessCommand(key, 5).execute(); + new SuccessCommand(key, 5).execute(); + new SuccessCommand(key, 5).execute(); + + Thread.sleep(2000); + + List> os = new ArrayList>(); + TestSubscriber testSubscriber = new TestSubscriber(); + + os.add(new SuccessCommand(key, 10).observe()); + os.add(new SuccessCommand(key, 20).observe()); + os.add(new SuccessCommand(key, 10).observe()); + os.add(new TimeoutCommand(key).observe()); + os.add(new SuccessCommand(key, 15).observe()); + os.add(new FailureCommand(key, 10).observe()); + os.add(new TimeoutCommand(key).observe()); + os.add(new TimeoutCommand(key).observe()); + os.add(new TimeoutCommand(key).observe()); + os.add(new TimeoutCommand(key).observe()); + + Observable.merge(os).subscribe(testSubscriber); + + testSubscriber.awaitTerminalEvent(300, TimeUnit.MILLISECONDS); + testSubscriber.assertCompleted(); + testSubscriber.assertNoErrors(); + + Thread.sleep(100); //1 bucket roll + + int meanExecutionLatency = servoPublisher.getExecutionLatencyMeanMonitor("meanExecutionLatency").getValue().intValue(); + int p5ExecutionLatency = servoPublisher.getExecutionLatencyPercentileMonitor("p5ExecutionLatency", 5).getValue().intValue(); + int p25ExecutionLatency = servoPublisher.getExecutionLatencyPercentileMonitor("p25ExecutionLatency", 25).getValue().intValue(); + int p50ExecutionLatency = servoPublisher.getExecutionLatencyPercentileMonitor("p50ExecutionLatency", 50).getValue().intValue(); + int p75ExecutionLatency = servoPublisher.getExecutionLatencyPercentileMonitor("p75ExecutionLatency", 75).getValue().intValue(); + int p90ExecutionLatency = servoPublisher.getExecutionLatencyPercentileMonitor("p90ExecutionLatency", 90).getValue().intValue(); + int p99ExecutionLatency = servoPublisher.getExecutionLatencyPercentileMonitor("p99ExecutionLatency", 99).getValue().intValue(); + int p995ExecutionLatency = servoPublisher.getExecutionLatencyPercentileMonitor("p995ExecutionLatency", 99.5).getValue().intValue(); + System.out.println("Execution: Mean : " + meanExecutionLatency + ", p5 : " + p5ExecutionLatency + ", p25 : " + p25ExecutionLatency + ", p50 : " + p50ExecutionLatency + ", p75 : " + p75ExecutionLatency + ", p90 : " + p90ExecutionLatency + ", p99 : " + p99ExecutionLatency + ", p99.5 : " + p995ExecutionLatency); + + int meanTotalLatency = servoPublisher.getTotalLatencyMeanMonitor("meanTotalLatency").getValue().intValue(); + int p5TotalLatency = servoPublisher.getTotalLatencyPercentileMonitor("p5TotalLatency", 5).getValue().intValue(); + int p25TotalLatency = servoPublisher.getTotalLatencyPercentileMonitor("p25TotalLatency", 25).getValue().intValue(); + int p50TotalLatency = servoPublisher.getTotalLatencyPercentileMonitor("p50TotalLatency", 50).getValue().intValue(); + int p75TotalLatency = servoPublisher.getTotalLatencyPercentileMonitor("p75TotalLatency", 75).getValue().intValue(); + int p90TotalLatency = servoPublisher.getTotalLatencyPercentileMonitor("p90TotalLatency", 90).getValue().intValue(); + int p99TotalLatency = servoPublisher.getTotalLatencyPercentileMonitor("p99TotalLatency", 99).getValue().intValue(); + int p995TotalLatency = servoPublisher.getTotalLatencyPercentileMonitor("p995TotalLatency", 99.5).getValue().intValue(); + System.out.println("Total: Mean : " + meanTotalLatency + ", p5 : " + p5TotalLatency + ", p25 : " + p25TotalLatency + ", p50 : " + p50TotalLatency + ", p75 : " + p75TotalLatency + ", p90 : " + p90TotalLatency + ", p99 : " + p99TotalLatency + ", p99.5 : " + p995TotalLatency); + + + assertTrue(meanExecutionLatency > 10); + assertTrue(p5ExecutionLatency <= p25ExecutionLatency); + assertTrue(p25ExecutionLatency <= p50ExecutionLatency); + assertTrue(p50ExecutionLatency <= p75ExecutionLatency); + assertTrue(p75ExecutionLatency <= p90ExecutionLatency); + assertTrue(p90ExecutionLatency <= p99ExecutionLatency); + assertTrue(p99ExecutionLatency <= p995ExecutionLatency); + + assertTrue(meanTotalLatency > 10); + assertTrue(p5TotalLatency <= p25TotalLatency); + assertTrue(p25TotalLatency <= p50TotalLatency); + assertTrue(p50TotalLatency <= p75TotalLatency); + assertTrue(p75TotalLatency <= p90TotalLatency); + assertTrue(p90TotalLatency <= p99TotalLatency); + assertTrue(p99TotalLatency <= p995TotalLatency); + + assertTrue(meanExecutionLatency <= meanTotalLatency); + assertTrue(p5ExecutionLatency <= p5TotalLatency); + assertTrue(p25ExecutionLatency <= p25TotalLatency); + assertTrue(p50ExecutionLatency <= p50TotalLatency); + assertTrue(p75ExecutionLatency <= p75TotalLatency); + assertTrue(p90ExecutionLatency <= p90TotalLatency); + assertTrue(p99ExecutionLatency <= p99TotalLatency); + assertTrue(p995ExecutionLatency <= p995TotalLatency); + } + + static class SampleCommand extends HystrixCommand { + boolean shouldFail; + int latencyToAdd; + + protected SampleCommand(HystrixCommandKey key, boolean shouldFail, int latencyToAdd) { + super(Setter.withGroupKey(groupKey).andCommandKey(key).andCommandPropertiesDefaults(propertiesSetter)); + this.shouldFail = shouldFail; + this.latencyToAdd = latencyToAdd; + } + + @Override + protected Integer run() throws Exception { + if (shouldFail) { + throw new RuntimeException("command failure"); + } else { + Thread.sleep(latencyToAdd); + return 1; + } + } + + @Override + protected Integer getFallback() { + return 99; + } + } + + static class SuccessCommand extends SampleCommand { + protected SuccessCommand(HystrixCommandKey key) { + super(key, false, 0); + } + + public SuccessCommand(HystrixCommandKey key, int latencyToAdd) { + super(key, false, latencyToAdd); + } + } + + static class FailureCommand extends SampleCommand { + protected FailureCommand(HystrixCommandKey key) { + super(key, true, 0); + } + + public FailureCommand(HystrixCommandKey key, int latencyToAdd) { + super(key, true, latencyToAdd); + } + } + + static class TimeoutCommand extends SampleCommand { + protected TimeoutCommand(HystrixCommandKey key) { + super(key, false, 400); //exceeds 100ms timeout + } + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/README.md b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/README.md new file mode 100644 index 0000000..3f167b4 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/README.md @@ -0,0 +1,25 @@ +# hystrix-yammer-metrics-publisher + +This is an implementation of [HystrixMetricsPublisher](http://netflix.github.com/Hystrix/javadoc/index.html?com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisher.html) that publishes metrics using [Yammer Metrics](http://metrics.codahale.com) version 2. If you are using Coda Hale Metrics version 3, please use the [hystrix-codahale-metrics-publisher](../hystrix-codahale-metrics-publisher) module instead. + +See the [Metrics & Monitoring](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring) Wiki for more information. + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hystrix-yammer-metrics-publisher%22). + +Example for Maven: + +```xml + + com.netflix.hystrix + hystrix-yammer-metrics-publisher + 1.1.2 + +``` + +and for Ivy: + +```xml + +``` \ No newline at end of file diff --git a/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/build.gradle b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/build.gradle new file mode 100644 index 0000000..8a7d098 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compileApi project(':hystrix-core') + compileApi 'com.yammer.metrics:metrics-core:2.2.0' +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisher.java b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisher.java new file mode 100644 index 0000000..5ed3fed --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisher.java @@ -0,0 +1,64 @@ +/** + * 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.contrib.yammermetricspublisher; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCollapser; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCommand; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherThreadPool; +import com.yammer.metrics.Metrics; +import com.yammer.metrics.core.MetricsRegistry; + +/** + * Yammer Metrics (https://github.com/codahale/metrics) implementation of {@link HystrixMetricsPublisher}. + */ +public class HystrixYammerMetricsPublisher extends HystrixMetricsPublisher { + private final MetricsRegistry metricsRegistry; + + public HystrixYammerMetricsPublisher() { + this(Metrics.defaultRegistry()); + } + + public HystrixYammerMetricsPublisher(MetricsRegistry metricsRegistry) { + this.metricsRegistry = metricsRegistry; + } + + @Override + public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + return new HystrixYammerMetricsPublisherCommand(commandKey, commandGroupKey, metrics, circuitBreaker, properties, metricsRegistry); + } + + @Override + public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + return new HystrixYammerMetricsPublisherThreadPool(threadPoolKey, metrics, properties, metricsRegistry); + } + + @Override + public HystrixMetricsPublisherCollapser getMetricsPublisherForCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { + return new HystrixYammerMetricsPublisherCollapser(collapserKey, metrics, properties, metricsRegistry); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherCollapser.java b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherCollapser.java new file mode 100644 index 0000000..87d6b5a --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherCollapser.java @@ -0,0 +1,273 @@ +/** + * 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.contrib.yammermetricspublisher; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCollapser; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import com.yammer.metrics.core.Gauge; +import com.yammer.metrics.core.MetricName; +import com.yammer.metrics.core.MetricsRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +/** + * Implementation of {@link HystrixMetricsPublisherCollapser} using Yammer Metrics + * + * + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-yammer-metrics-publisher, + * the code below may reference a HystrixRollingNumberEvent that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + */ +public class HystrixYammerMetricsPublisherCollapser implements HystrixMetricsPublisherCollapser { + private final HystrixCollapserKey key; + private final HystrixCollapserMetrics metrics; + private final HystrixCollapserProperties properties; + private final MetricsRegistry metricsRegistry; + private final String metricType; + + static final Logger logger = LoggerFactory.getLogger(HystrixYammerMetricsPublisherCollapser.class); + + public HystrixYammerMetricsPublisherCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties, MetricsRegistry metricsRegistry) { + this.key = collapserKey; + this.metrics = metrics; + this.properties = properties; + this.metricsRegistry = metricsRegistry; + this.metricType = key.name(); + } + + @Override + public void initialize() { + // allow monitor to know exactly at what point in time these stats are for so they can be plotted accurately + metricsRegistry.newGauge(createMetricName("currentTime"), new Gauge() { + @Override + public Long value() { + return System.currentTimeMillis(); + } + }); + + // cumulative counts + safelyCreateCumulativeCountForEvent("countRequestsBatched", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSER_REQUEST_BATCHED; + } + }); + safelyCreateCumulativeCountForEvent("countBatches", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSER_BATCH; + } + }); + safelyCreateCumulativeCountForEvent("countResponsesFromCache", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.RESPONSE_FROM_CACHE; + } + }); + + // rolling counts + safelyCreateRollingCountForEvent("rollingRequestsBatched", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSER_REQUEST_BATCHED; + } + }); + safelyCreateRollingCountForEvent("rollingBatches", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.COLLAPSER_BATCH; + } + }); + safelyCreateRollingCountForEvent("rollingCountResponsesFromCache", new Func0() { + @Override + public HystrixRollingNumberEvent call() { + return HystrixRollingNumberEvent.RESPONSE_FROM_CACHE; + } + }); + + // batch size metrics + metricsRegistry.newGauge(createMetricName("batchSize_mean"), new Gauge() { + @Override + public Integer value() { + return metrics.getBatchSizeMean(); + } + }); + metricsRegistry.newGauge(createMetricName("batchSize_percentile_25"), new Gauge() { + @Override + public Integer value() { + return metrics.getBatchSizePercentile(25); + } + }); + metricsRegistry.newGauge(createMetricName("batchSize_percentile_50"), new Gauge() { + @Override + public Integer value() { + return metrics.getBatchSizePercentile(50); + } + }); + metricsRegistry.newGauge(createMetricName("batchSize_percentile_75"), new Gauge() { + @Override + public Integer value() { + return metrics.getBatchSizePercentile(75); + } + }); + metricsRegistry.newGauge(createMetricName("batchSize_percentile_90"), new Gauge() { + @Override + public Integer value() { + return metrics.getBatchSizePercentile(90); + } + }); + metricsRegistry.newGauge(createMetricName("batchSize_percentile_99"), new Gauge() { + @Override + public Integer value() { + return metrics.getBatchSizePercentile(99); + } + }); + metricsRegistry.newGauge(createMetricName("batchSize_percentile_995"), new Gauge() { + @Override + public Integer value() { + return metrics.getBatchSizePercentile(99.5); + } + }); + + // shard size metrics + metricsRegistry.newGauge(createMetricName("shardSize_mean"), new Gauge() { + @Override + public Integer value() { + return metrics.getShardSizeMean(); + } + }); + metricsRegistry.newGauge(createMetricName("shardSize_percentile_25"), new Gauge() { + @Override + public Integer value() { + return metrics.getShardSizePercentile(25); + } + }); + metricsRegistry.newGauge(createMetricName("shardSize_percentile_50"), new Gauge() { + @Override + public Integer value() { + return metrics.getShardSizePercentile(50); + } + }); + metricsRegistry.newGauge(createMetricName("shardSize_percentile_75"), new Gauge() { + @Override + public Integer value() { + return metrics.getShardSizePercentile(75); + } + }); + metricsRegistry.newGauge(createMetricName("shardSize_percentile_90"), new Gauge() { + @Override + public Integer value() { + return metrics.getShardSizePercentile(90); + } + }); + metricsRegistry.newGauge(createMetricName("shardSize_percentile_99"), new Gauge() { + @Override + public Integer value() { + return metrics.getShardSizePercentile(99); + } + }); + metricsRegistry.newGauge(createMetricName("shardSize_percentile_995"), new Gauge() { + @Override + public Integer value() { + return metrics.getShardSizePercentile(99.5); + } + }); + + // properties (so the values can be inspected and monitored) + metricsRegistry.newGauge(createMetricName("propertyValue_rollingStatisticalWindowInMilliseconds"), new Gauge() { + @Override + public Number value() { + return properties.metricsRollingStatisticalWindowInMilliseconds().get(); + } + }); + + metricsRegistry.newGauge(createMetricName("propertyValue_requestCacheEnabled"), new Gauge() { + @Override + public Boolean value() { + return properties.requestCacheEnabled().get(); + } + }); + + metricsRegistry.newGauge(createMetricName("propertyValue_maxRequestsInBatch"), new Gauge() { + @Override + public Number value() { + return properties.maxRequestsInBatch().get(); + } + }); + + metricsRegistry.newGauge(createMetricName("propertyValue_timerDelayInMilliseconds"), new Gauge() { + @Override + public Number value() { + return properties.timerDelayInMilliseconds().get(); + } + }); + } + + protected MetricName createMetricName(String name) { + return new MetricName("", metricType, name); + } + + protected void createCumulativeCountForEvent(final String name, final HystrixRollingNumberEvent event) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + return metrics.getCumulativeCount(event); + } + }); + } + + protected void safelyCreateCumulativeCountForEvent(final String name, final Func0 eventThunk) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + try { + return metrics.getCumulativeCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing Yammer metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }); + } + + protected void createRollingCountForEvent(final String name, final HystrixRollingNumberEvent event) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + return metrics.getRollingCount(event); + } + }); + } + + protected void safelyCreateRollingCountForEvent(final String name, final Func0 eventThunk) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + try { + return metrics.getRollingCount(eventThunk.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing Yammer metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherCommand.java b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherCommand.java new file mode 100644 index 0000000..bf912bb --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherCommand.java @@ -0,0 +1,525 @@ +/** + * 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.contrib.yammermetricspublisher; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherCommand; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import com.yammer.metrics.core.Gauge; +import com.yammer.metrics.core.MetricName; +import com.yammer.metrics.core.MetricsRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +/** + * Implementation of {@link HystrixMetricsPublisherCommand} using Yammer Metrics (https://github.com/codahale/metrics) + * + * An implementation note. If there's a version mismatch between hystrix-core and hystrix-yammer-metrics-publisher, + * the code below may reference a HystrixRollingNumberEvent that does not exist in hystrix-core. If this happens, + * a j.l.NoSuchFieldError occurs. Since this data is not being generated by hystrix-core, it's safe to count it as 0 + * and we should log an error to get users to update their dependency set. + + */ +public class HystrixYammerMetricsPublisherCommand implements HystrixMetricsPublisherCommand { + private final HystrixCommandKey key; + private final HystrixCommandGroupKey commandGroupKey; + private final HystrixCommandMetrics metrics; + private final HystrixCircuitBreaker circuitBreaker; + private final HystrixCommandProperties properties; + private final MetricsRegistry metricsRegistry; + private final String metricGroup; + private final String metricType; + + static final Logger logger = LoggerFactory.getLogger(HystrixYammerMetricsPublisherCommand.class); + + public HystrixYammerMetricsPublisherCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties, MetricsRegistry metricsRegistry) { + this.key = commandKey; + this.commandGroupKey = commandGroupKey; + this.metrics = metrics; + this.circuitBreaker = circuitBreaker; + this.properties = properties; + this.metricsRegistry = metricsRegistry; + this.metricGroup = "HystrixCommand"; + this.metricType = key.name(); + } + + @Override + public void initialize() { + metricsRegistry.newGauge(createMetricName("isCircuitBreakerOpen"), new Gauge() { + @Override + public Boolean value() { + return circuitBreaker.isOpen(); + } + }); + + // allow monitor to know exactly at what point in time these stats are for so they can be plotted accurately + metricsRegistry.newGauge(createMetricName("currentTime"), new Gauge() { + @Override + public Long value() { + return System.currentTimeMillis(); + } + }); + + // cumulative counts + safelyCreateCumulativeGauge("countBadRequests", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.BAD_REQUEST; + } + }); + safelyCreateCumulativeGauge("countCollapsedRequests", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.COLLAPSED; + } + }); + safelyCreateCumulativeGauge("countEmit", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.EMIT; + } + }); + safelyCreateCumulativeGauge("countExceptionsThrown", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.EXCEPTION_THROWN; + } + }); + safelyCreateCumulativeGauge("countFailure", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FAILURE; + } + }); + safelyCreateCumulativeGauge("countFallbackEmit", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_EMIT; + } + }); + safelyCreateCumulativeGauge("countFallbackFailure", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_FAILURE; + } + }); + safelyCreateCumulativeGauge("countFallbackMissing", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_MISSING; + } + }); + safelyCreateCumulativeGauge("countFallbackRejection", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_REJECTION; + } + }); + safelyCreateCumulativeGauge("countFallbackSuccess", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_SUCCESS; + } + }); + safelyCreateCumulativeGauge("countResponsesFromCache", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.RESPONSE_FROM_CACHE; + } + }); + safelyCreateCumulativeGauge("countSemaphoreRejected", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SEMAPHORE_REJECTED; + } + }); + safelyCreateCumulativeGauge("countShortCircuited", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SHORT_CIRCUITED; + } + }); + safelyCreateCumulativeGauge("countSuccess", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SUCCESS; + } + }); + safelyCreateCumulativeGauge("countThreadPoolRejected", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.THREAD_POOL_REJECTED; + } + }); + safelyCreateCumulativeGauge("countTimeout", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.TIMEOUT; + } + }); + + // rolling counts + safelyCreateRollingGauge("rollingCountBadRequests", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.BAD_REQUEST; + } + }); + safelyCreateRollingGauge("rollingCountCollapsedRequests", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.COLLAPSED; + } + }); + safelyCreateRollingGauge("rollingCountExceptionsThrown", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.EXCEPTION_THROWN; + } + }); + safelyCreateRollingGauge("rollingCountFailure", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FAILURE; + } + }); + safelyCreateRollingGauge("rollingCountFallbackFailure", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_FAILURE; + } + }); + safelyCreateRollingGauge("rollingCountFallbackMissing", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_MISSING; + } + }); + safelyCreateRollingGauge("rollingCountFallbackRejection", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_REJECTION; + } + }); + safelyCreateRollingGauge("rollingCountFallbackSuccess", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.FALLBACK_SUCCESS; + } + }); + safelyCreateRollingGauge("rollingCountResponsesFromCache", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.RESPONSE_FROM_CACHE; + } + }); + safelyCreateRollingGauge("rollingCountSemaphoreRejected", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SEMAPHORE_REJECTED; + } + }); + safelyCreateRollingGauge("rollingCountShortCircuited", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SHORT_CIRCUITED; + } + }); + safelyCreateRollingGauge("rollingCountSuccess", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.SUCCESS; + } + }); + safelyCreateRollingGauge("rollingCountThreadPoolRejected", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.THREAD_POOL_REJECTED; + } + }); + safelyCreateRollingGauge("rollingCountTimeout", new Func0() { + @Override + public HystrixEventType call() { + return HystrixEventType.TIMEOUT; + } + }); + + // the number of executionSemaphorePermits in use right now + createCurrentValueGauge("executionSemaphorePermitsInUse", currentConcurrentExecutionCountThunk); + + // error percentage derived from current metrics + createCurrentValueGauge("errorPercentage", errorPercentageThunk); + + // latency metrics + createExecutionLatencyMeanGauge("latencyExecute_mean"); + + createExecutionLatencyPercentileGauge("latencyExecute_percentile_5", 5); + createExecutionLatencyPercentileGauge("latencyExecute_percentile_25", 25); + createExecutionLatencyPercentileGauge("latencyExecute_percentile_50", 50); + createExecutionLatencyPercentileGauge("latencyExecute_percentile_75", 75); + createExecutionLatencyPercentileGauge("latencyExecute_percentile_90", 90); + createExecutionLatencyPercentileGauge("latencyExecute_percentile_99", 99); + createExecutionLatencyPercentileGauge("latencyExecute_percentile_995", 99.5); + + createTotalLatencyMeanGauge("latencyTotal_mean"); + + createTotalLatencyPercentileGauge("latencyTotal_percentile_5", 5); + createTotalLatencyPercentileGauge("latencyTotal_percentile_25", 25); + createTotalLatencyPercentileGauge("latencyTotal_percentile_50", 50); + createTotalLatencyPercentileGauge("latencyTotal_percentile_75", 75); + createTotalLatencyPercentileGauge("latencyTotal_percentile_90", 90); + createTotalLatencyPercentileGauge("latencyTotal_percentile_99", 99); + createTotalLatencyPercentileGauge("latencyTotal_percentile_995", 99.5); + + // group + metricsRegistry.newGauge(createMetricName("commandGroup"), new Gauge() { + @Override + public String value() { + return commandGroupKey != null ? commandGroupKey.name() : null; + } + }); + + // properties (so the values can be inspected and monitored) + metricsRegistry.newGauge(createMetricName("propertyValue_rollingStatisticalWindowInMilliseconds"), new Gauge() { + @Override + public Number value() { + return properties.metricsRollingStatisticalWindowInMilliseconds().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_circuitBreakerRequestVolumeThreshold"), new Gauge() { + @Override + public Number value() { + return properties.circuitBreakerRequestVolumeThreshold().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_circuitBreakerSleepWindowInMilliseconds"), new Gauge() { + @Override + public Number value() { + return properties.circuitBreakerSleepWindowInMilliseconds().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_circuitBreakerErrorThresholdPercentage"), new Gauge() { + @Override + public Number value() { + return properties.circuitBreakerErrorThresholdPercentage().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_circuitBreakerForceOpen"), new Gauge() { + @Override + public Boolean value() { + return properties.circuitBreakerForceOpen().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_circuitBreakerForceClosed"), new Gauge() { + @Override + public Boolean value() { + return properties.circuitBreakerForceClosed().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_executionTimeoutInMilliseconds"), new Gauge() { + @Override + public Number value() { + return properties.executionTimeoutInMilliseconds().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_executionIsolationThreadTimeoutInMilliseconds"), new Gauge() { + @Override + public Number value() { + return properties.executionTimeoutInMilliseconds().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_executionIsolationStrategy"), new Gauge() { + @Override + public String value() { + return properties.executionIsolationStrategy().get().name(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_metricsRollingPercentileEnabled"), new Gauge() { + @Override + public Boolean value() { + return properties.metricsRollingPercentileEnabled().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_requestCacheEnabled"), new Gauge() { + @Override + public Boolean value() { + return properties.requestCacheEnabled().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_requestLogEnabled"), new Gauge() { + @Override + public Boolean value() { + return properties.requestLogEnabled().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_executionIsolationSemaphoreMaxConcurrentRequests"), new Gauge() { + @Override + public Number value() { + return properties.executionIsolationSemaphoreMaxConcurrentRequests().get(); + } + }); + metricsRegistry.newGauge(createMetricName("propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests"), new Gauge() { + @Override + public Number value() { + return properties.fallbackIsolationSemaphoreMaxConcurrentRequests().get(); + } + }); + } + + protected MetricName createMetricName(String name) { + return new MetricName(metricGroup, metricType, name); + } + + @Deprecated + protected void createCumulativeCountForEvent(String name, final HystrixRollingNumberEvent event) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + return metrics.getCumulativeCount(event); + } + }); + } + + protected void createCumulativeGauge(final String name, final HystrixEventType eventType) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + return metrics.getCumulativeCount(HystrixRollingNumberEvent.from(eventType)); + } + }); + } + + protected void safelyCreateCumulativeGauge(final String name, final Func0 eventThunk) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + try { + HystrixRollingNumberEvent eventType = HystrixRollingNumberEvent.from(eventThunk.call()); + return metrics.getCumulativeCount(eventType); + } catch (NoSuchFieldError error) { + logger.error("While publishing Yammer metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }); + } + + @Deprecated + protected void createRollingGauge(String name, final HystrixRollingNumberEvent event) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + return metrics.getRollingCount(event); + } + }); + } + + protected void createRollingGauge(final String name, final HystrixEventType eventType) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + return metrics.getRollingCount(HystrixRollingNumberEvent.from(eventType)); + } + }); + } + + protected void safelyCreateRollingGauge(final String name, final Func0 eventThunk) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Long value() { + try { + HystrixRollingNumberEvent eventType = HystrixRollingNumberEvent.from(eventThunk.call()); + return metrics.getRollingCount(eventType); + } catch (NoSuchFieldError error) { + logger.error("While publishing Yammer metrics, error looking up eventType for : {}. Please check that all Hystrix versions are the same!", name); + return 0L; + } + } + }); + } + + protected void createExecutionLatencyMeanGauge(final String name) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Integer value() { + return metrics.getExecutionTimeMean(); + } + }); + } + + protected void createExecutionLatencyPercentileGauge(final String name, final double percentile) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Integer value() { + return metrics.getExecutionTimePercentile(percentile); + } + }); + } + + protected void createTotalLatencyMeanGauge(final String name) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Integer value() { + return metrics.getTotalTimeMean(); + } + }); + } + + protected void createTotalLatencyPercentileGauge(final String name, final double percentile) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Integer value() { + return metrics.getTotalTimePercentile(percentile); + } + }); + } + + protected final Func0 currentConcurrentExecutionCountThunk = new Func0() { + @Override + public Integer call() { + return metrics.getCurrentConcurrentExecutionCount(); + } + }; + + protected final Func0 rollingMaxConcurrentExecutionCountThunk = new Func0() { + @Override + public Long call() { + return metrics.getRollingMaxConcurrentExecutions(); + } + }; + + protected final Func0 errorPercentageThunk = new Func0() { + @Override + public Integer call() { + return metrics.getHealthCounts().getErrorPercentage(); + } + }; + + protected void createCurrentValueGauge(final String name, final Func0 metricToEvaluate) { + metricsRegistry.newGauge(createMetricName(name), new Gauge() { + @Override + public Integer value() { + return metricToEvaluate.call(); + } + }); + } +} diff --git a/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherThreadPool.java b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherThreadPool.java new file mode 100644 index 0000000..aa2b996 --- /dev/null +++ b/Hystrix-master/hystrix-contrib/hystrix-yammer-metrics-publisher/src/main/java/com/netflix/hystrix/contrib/yammermetricspublisher/HystrixYammerMetricsPublisherThreadPool.java @@ -0,0 +1,184 @@ +/** + * 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.contrib.yammermetricspublisher; + +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherThreadPool; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import com.yammer.metrics.core.Gauge; +import com.yammer.metrics.core.MetricName; +import com.yammer.metrics.core.MetricsRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link HystrixMetricsPublisherThreadPool} using Yammer Metrics (https://github.com/codahale/metrics) + */ +public class HystrixYammerMetricsPublisherThreadPool implements HystrixMetricsPublisherThreadPool { + private final HystrixThreadPoolKey key; + private final HystrixThreadPoolMetrics metrics; + private final HystrixThreadPoolProperties properties; + private final MetricsRegistry metricsRegistry; + private final String metricGroup; + private final String metricType; + + static final Logger logger = LoggerFactory.getLogger(HystrixYammerMetricsPublisherThreadPool.class); + + + public HystrixYammerMetricsPublisherThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties, MetricsRegistry metricsRegistry) { + this.key = threadPoolKey; + this.metrics = metrics; + this.properties = properties; + this.metricsRegistry = metricsRegistry; + this.metricGroup = "HystrixThreadPool"; + this.metricType = key.name(); + } + + @Override + public void initialize() { + metricsRegistry.newGauge(createMetricName("name"), new Gauge() { + @Override + public String value() { + return key.name(); + } + }); + + // allow monitor to know exactly at what point in time these stats are for so they can be plotted accurately + metricsRegistry.newGauge(createMetricName("currentTime"), new Gauge() { + @Override + public Long value() { + return System.currentTimeMillis(); + } + }); + + metricsRegistry.newGauge(createMetricName("threadActiveCount"), new Gauge() { + @Override + public Number value() { + return metrics.getCurrentActiveCount(); + } + }); + + metricsRegistry.newGauge(createMetricName("completedTaskCount"), new Gauge() { + @Override + public Number value() { + return metrics.getCurrentCompletedTaskCount(); + } + }); + + metricsRegistry.newGauge(createMetricName("largestPoolSize"), new Gauge() { + @Override + public Number value() { + return metrics.getCurrentLargestPoolSize(); + } + }); + + metricsRegistry.newGauge(createMetricName("totalTaskCount"), new Gauge() { + @Override + public Number value() { + return metrics.getCurrentTaskCount(); + } + }); + + metricsRegistry.newGauge(createMetricName("queueSize"), new Gauge() { + @Override + public Number value() { + return metrics.getCurrentQueueSize(); + } + }); + + metricsRegistry.newGauge(createMetricName("rollingMaxActiveThreads"), new Gauge() { + @Override + public Number value() { + return metrics.getRollingMaxActiveThreads(); + } + }); + + metricsRegistry.newGauge(createMetricName("countThreadsExecuted"), new Gauge() { + @Override + public Number value() { + return metrics.getCumulativeCountThreadsExecuted(); + } + }); + + metricsRegistry.newGauge(createMetricName("rollingCountCommandsRejected"), new Gauge() { + @Override + public Number value() { + try { + return metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED); + } catch (NoSuchFieldError error) { + logger.error("While publishing Yammer metrics, error looking up eventType for : rollingCountCommandsRejected. Please check that all Hystrix versions are the same!"); + return 0L; + } + } + }); + + metricsRegistry.newGauge(createMetricName("rollingCountThreadsExecuted"), new Gauge() { + @Override + public Number value() { + return metrics.getRollingCountThreadsExecuted(); + } + }); + + // properties + metricsRegistry.newGauge(createMetricName("propertyValue_corePoolSize"), new Gauge() { + @Override + public Number value() { + return properties.coreSize().get(); + } + }); + + metricsRegistry.newGauge(createMetricName("propertyValue_maximumSize"), new Gauge() { + @Override + public Number value() { + return properties.maximumSize().get(); + } + }); + + metricsRegistry.newGauge(createMetricName("propertyValue_actualMaximumSize"), new Gauge() { + @Override + public Number value() { + return properties.actualMaximumSize(); + } + }); + + metricsRegistry.newGauge(createMetricName("propertyValue_keepAliveTimeInMinutes"), new Gauge() { + @Override + public Number value() { + return properties.keepAliveTimeMinutes().get(); + } + }); + + metricsRegistry.newGauge(createMetricName("propertyValue_queueSizeRejectionThreshold"), new Gauge() { + @Override + public Number value() { + return properties.queueSizeRejectionThreshold().get(); + } + }); + + metricsRegistry.newGauge(createMetricName("propertyValue_maxQueueSize"), new Gauge() { + @Override + public Number value() { + return properties.maxQueueSize().get(); + } + }); + } + + protected MetricName createMetricName(String name) { + return new MetricName(metricGroup, metricType, name); + } +} diff --git a/Hystrix-master/hystrix-core/README.md b/Hystrix-master/hystrix-core/README.md new file mode 100644 index 0000000..eac0ae7 --- /dev/null +++ b/Hystrix-master/hystrix-core/README.md @@ -0,0 +1,41 @@ +## hystrix-core + +This is the core module of Hystrix. + +Key classes you're most likely to interact with are: + +- HystrixCommand [Source](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java) [Javadoc](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html) +- HystrixObservableCommand [Source](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java) [Javadoc](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/HystrixObservableCommand.html) +- HystrixCollapser [Source](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java) [Javadoc](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/HystrixCollapser.html) +- HystrixRequestLog [Source](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java) [Javadoc](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/HystrixRequestLog.html) +- HystrixPlugins [Source](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/HystrixPlugins.java) [Javadoc](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/strategy/HystrixPlugins.html) +- HystrixRequestContext [Source](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestContext.java) [Javadoc](http://netflix.github.com/Hystrix/javadoc/com/netflix/hystrix/strategy/concurrency/HystrixRequestContext.html) + +## Documentation + +A general project README can be found at the [project home](https://github.com/Netflix/Hystrix). + +The [Wiki](https://github.com/Netflix/Hystrix/wiki) contains detailed documentation. + + +## Maven Central + +Binaries and dependencies for this module can be found on [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20a%3A%22hystrix-core%22). + +__GroupId: com.netflix.hystrix__ +__ArtifactId: hystrix-core__ + +Example for Maven: + +```xml + + com.netflix.hystrix + hystrix-core + 1.2.0 + +``` +and for Ivy: + +```xml + +``` diff --git a/Hystrix-master/hystrix-core/build.gradle b/Hystrix-master/hystrix-core/build.gradle new file mode 100644 index 0000000..8955c78 --- /dev/null +++ b/Hystrix-master/hystrix-core/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'osgi' +apply plugin: 'me.champeau.gradle.jmh' + +dependencies { + compileApi 'com.netflix.archaius:archaius-core:0.4.1' + compileApi 'io.reactivex:rxjava:1.2.0' + compile 'org.slf4j:slf4j-api:1.7.0' + compileApi 'org.hdrhistogram:HdrHistogram:2.1.9' + testCompile 'junit:junit-dep:4.10' + testCompile project(':hystrix-junit') +} + + +javadoc { + // the exclude isn't working, nor is there a subPackages options as docs suggest there should be + // we do not want the com.netflix.hystrix.util package include + exclude '**/util/**' + + options { + doclet = "org.benjchristensen.doclet.DocletExclude" + docletpath = [rootProject.file('./gradle/doclet-exclude.jar')] + stylesheetFile = rootProject.file('./gradle/javadocStyleSheet.css') + windowTitle = "Hystrix Javadoc ${project.version}" + } + options.addStringOption('top').value = '

Hystrix: Latency and Fault Tolerance for Distributed Systems

' +} + +jar { + manifest { + name = 'hystrix-core' + instruction 'Bundle-Vendor', 'Netflix' + instruction 'Bundle-DocURL', 'https://github.com/Netflix/Hystrix' + instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' + instruction 'Eclipse-ExtensibleAPI', 'true' + instruction 'Embed-Dependency', '*;scope=compile' + } +} + +jmh { + fork = 10 + iterations = 3 + jmhVersion = '1.15' + profilers = ['gc'] + threads = 8 + warmup = '1s' + warmupBatchSize = 1 + warmupIterations = 5 +} diff --git a/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CollapserPerfTest.java b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CollapserPerfTest.java new file mode 100644 index 0000000..3e15551 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CollapserPerfTest.java @@ -0,0 +1,164 @@ +/** + * 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.perf; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; +import rx.Observable; +import rx.Subscription; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class CollapserPerfTest { + @State(Scope.Benchmark) + public static class ThreadPoolState { + HystrixThreadPool hystrixThreadPool; + + @Setup + public void setUp() { + hystrixThreadPool = new HystrixThreadPool.HystrixThreadPoolDefault( + HystrixThreadPoolKey.Factory.asKey("PERF") + , HystrixThreadPoolProperties.Setter().withCoreSize(100)); + } + + @TearDown + public void tearDown() { + hystrixThreadPool.getExecutor().shutdownNow(); + } + } + + @State(Scope.Thread) + public static class CollapserState { + @Param({"1", "10", "100", "1000"}) + int numToCollapse; + + @Param({"1", "1000", "1000000"}) + int blackholeConsumption; + + HystrixRequestContext reqContext; + Observable executionHandle; + + @Setup(Level.Invocation) + public void setUp() { + reqContext = HystrixRequestContext.initializeContext(); + + List> os = new ArrayList>(); + + for (int i = 0; i < numToCollapse; i++) { + IdentityCollapser collapser = new IdentityCollapser(i, blackholeConsumption); + os.add(collapser.observe()); + } + + executionHandle = Observable.merge(os); + } + + @TearDown(Level.Invocation) + public void tearDown() { + reqContext.shutdown(); + } + + } + + private static class IdentityCollapser extends HystrixCollapser, String, String> { + + private final int arg; + private final int blackholeConsumption; + + IdentityCollapser(int arg, int blackholeConsumption) { + super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("COLLAPSER")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withMaxRequestsInBatch(1000).withTimerDelayInMilliseconds(1))); + this.arg = arg; + this.blackholeConsumption = blackholeConsumption; + } + + @Override + public String getRequestArgument() { + return arg + ""; + } + + @Override + protected HystrixCommand> createCommand(Collection> collapsedRequests) { + List args = new ArrayList(); + for (CollapsedRequest collapsedReq: collapsedRequests) { + args.add(collapsedReq.getArgument()); + } + return new BatchCommand(args, blackholeConsumption); + } + + @Override + protected void mapResponseToRequests(List batchResponse, Collection> collapsedRequests) { + for (CollapsedRequest collapsedReq: collapsedRequests) { + String requestArg = collapsedReq.getArgument(); + String response = ""; + for (String responsePiece: batchResponse) { + if (responsePiece.startsWith(requestArg + ":")) { + response = responsePiece; + break; + } + } + collapsedReq.setResponse(response); + } + } + } + + private static class BatchCommand extends HystrixCommand> { + private final List inputArgs; + private final int blackholeConsumption; + + BatchCommand(List inputArgs, int blackholeConsumption) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("PERF")).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("PERF"))); + this.inputArgs = inputArgs; + this.blackholeConsumption = blackholeConsumption; + } + + @Override + protected List run() throws Exception { + Blackhole.consumeCPU(blackholeConsumption); + List toReturn = new ArrayList(); + for (String inputArg: inputArgs) { + toReturn.add(inputArg + ":1"); + } + return toReturn; + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.SECONDS) + public List observeCollapsedAndWait(CollapserState collapserState, ThreadPoolState threadPoolState) { + return collapserState.executionHandle.toList().toBlocking().single(); + } +} diff --git a/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandConstructionPerfTest.java b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandConstructionPerfTest.java new file mode 100644 index 0000000..c25f26d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandConstructionPerfTest.java @@ -0,0 +1,42 @@ +/** + * Copyright 2014 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.perf; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; + +import java.util.concurrent.TimeUnit; + +public class CommandConstructionPerfTest { + + static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Group"); + + @Benchmark + @BenchmarkMode({Mode.SingleShotTime}) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + public HystrixCommand constructHystrixCommandByGroupKeyOnly() { + return new HystrixCommand(groupKey) { + @Override + protected Integer run() throws Exception { + return 1; + } + }; + } +} diff --git a/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandExecutionAndConcurrentMetricsReadPerfTest.java b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandExecutionAndConcurrentMetricsReadPerfTest.java new file mode 100644 index 0000000..31e5731 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandExecutionAndConcurrentMetricsReadPerfTest.java @@ -0,0 +1,152 @@ +/** + * 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.perf; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Group; +import org.openjdk.jmh.annotations.GroupThreads; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.concurrent.TimeUnit; + +public class CommandExecutionAndConcurrentMetricsReadPerfTest { + @State(Scope.Thread) + public static class CommandState { + HystrixCommand command; + + @Param({"THREAD", "SEMAPHORE"}) + public HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy; + + + @Setup(Level.Invocation) + public void setUp() { + command = new HystrixCommand( + HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("PERF")) + .andCommandPropertiesDefaults( + HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(isolationStrategy) + .withRequestCacheEnabled(true) + .withRequestLogEnabled(true) + .withCircuitBreakerEnabled(true) + .withCircuitBreakerForceOpen(false) + ) + .andThreadPoolPropertiesDefaults( + HystrixThreadPoolProperties.Setter() + .withCoreSize(100) + ) + ) { + @Override + protected Integer run() throws Exception { + return 1; + } + + @Override + protected Integer getFallback() { + return 2; + } + }; + } + } + + @Benchmark + @Group("writeHeavy") + @GroupThreads(7) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer writeHeavyCommandExecution(CommandState state) { + return state.command.observe().toBlocking().first(); + } + + @Benchmark + @Group("writeHeavy") + @GroupThreads(1) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long writeHeavyReadMetrics(CommandState state) { + HystrixCommandMetrics metrics = state.command.getMetrics(); + return metrics.getExecutionTimeMean() + + metrics.getExecutionTimePercentile(50) + + metrics.getExecutionTimePercentile(75) + + metrics.getExecutionTimePercentile(99) + + metrics.getCumulativeCount(HystrixEventType.SUCCESS) + + metrics.getRollingCount(HystrixEventType.FAILURE) + + metrics.getRollingMaxConcurrentExecutions(); + } + + @Benchmark + @Group("evenSplit") + @GroupThreads(4) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer evenSplitOfWritesAndReadsCommandExecution(CommandState state) { + return state.command.observe().toBlocking().first(); + } + + @Benchmark + @Group("evenSplit") + @GroupThreads(4) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long evenSplitOfWritesAndReadsReadMetrics(CommandState state) { + HystrixCommandMetrics metrics = state.command.getMetrics(); + return metrics.getExecutionTimeMean() + + metrics.getExecutionTimePercentile(50) + + metrics.getExecutionTimePercentile(75) + + metrics.getExecutionTimePercentile(99) + + metrics.getCumulativeCount(HystrixEventType.SUCCESS) + + metrics.getRollingCount(HystrixEventType.FAILURE) + + metrics.getRollingMaxConcurrentExecutions(); + } + + @Benchmark + @Group("readHeavy") + @GroupThreads(1) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer readHeavyCommandExecution(CommandState state) { + return state.command.observe().toBlocking().first(); + } + + @Benchmark + @Group("readHeavy") + @GroupThreads(7) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Long readHeavyReadMetrics(CommandState state) { + HystrixCommandMetrics metrics = state.command.getMetrics(); + return metrics.getExecutionTimeMean() + + metrics.getExecutionTimePercentile(50) + + metrics.getExecutionTimePercentile(75) + + metrics.getExecutionTimePercentile(99) + + metrics.getCumulativeCount(HystrixEventType.SUCCESS) + + metrics.getRollingCount(HystrixEventType.FAILURE) + + metrics.getRollingMaxConcurrentExecutions(); + + } +} diff --git a/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandExecutionPerfTest.java b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandExecutionPerfTest.java new file mode 100644 index 0000000..cc40587 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/CommandExecutionPerfTest.java @@ -0,0 +1,333 @@ +/** + * Copyright 2014 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.perf; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; +import rx.Observable; +import rx.functions.Func0; +import rx.schedulers.Schedulers; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Note that the hystrixExecute test must be run on a forked JVM. Otherwise, the initial properties that get + * set for the command apply to all runs. This would leave the command as THREAD-isolated in all test, for example. + */ +public class CommandExecutionPerfTest { + + private static HystrixCommandProperties.Setter threadIsolatedCommandDefaults = HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) + .withRequestCacheEnabled(true) + .withRequestLogEnabled(true) + .withCircuitBreakerEnabled(true) + .withCircuitBreakerForceOpen(false); + + private static HystrixCommandProperties.Setter threadIsolatedFailFastCommandDefaults = HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) + .withRequestCacheEnabled(true) + .withRequestLogEnabled(true) + .withCircuitBreakerEnabled(true) + .withCircuitBreakerForceOpen(true); + + private static HystrixCommandProperties.Setter semaphoreIsolatedCommandDefaults = HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE) + .withRequestCacheEnabled(true) + .withRequestLogEnabled(true) + .withCircuitBreakerEnabled(true) + .withCircuitBreakerForceOpen(false); + + private static HystrixCommandProperties.Setter semaphoreIsolatedFailFastCommandDefaults = HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE) + .withRequestCacheEnabled(true) + .withRequestLogEnabled(true) + .withCircuitBreakerEnabled(true) + .withCircuitBreakerForceOpen(true); + + private static HystrixThreadPoolProperties.Setter threadPoolDefaults = HystrixThreadPoolProperties.Setter() + .withCoreSize(100); + + private static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("PERF"); + + private static HystrixCommandProperties.Setter getCommandSetter(HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy, boolean forceOpen) { + switch (isolationStrategy) { + case THREAD: + if (forceOpen) { + return threadIsolatedFailFastCommandDefaults; + } else { + return threadIsolatedCommandDefaults; + } + default: + if (forceOpen) { + return semaphoreIsolatedFailFastCommandDefaults; + } else { + return semaphoreIsolatedCommandDefaults; + } + } + } + + @State(Scope.Thread) + public static class BlackholeState { + //amount of "work" to give to CPU + @Param({"1", "100", "10000"}) + public int blackholeConsumption; + } + + @State(Scope.Thread) + public static class CommandState { + HystrixCommand command; + HystrixRequestContext requestContext; + + @Param({"true", "false"}) + public boolean forceOpen; + + @Param({"true", "false"}) + public boolean setUpRequestContext; + + @Param({"THREAD", "SEMAPHORE"}) + public HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy; + + //amount of "work" to give to CPU + @Param({"1", "100", "10000"}) + public int blackholeConsumption; + + @Setup(Level.Invocation) + public void setUp() { + if (setUpRequestContext) { + requestContext = HystrixRequestContext.initializeContext(); + } + + command = new HystrixCommand( + HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("PERF")) + .andCommandPropertiesDefaults(getCommandSetter(isolationStrategy, forceOpen)) + .andThreadPoolPropertiesDefaults(threadPoolDefaults) + ) { + @Override + protected Integer run() throws Exception { + Blackhole.consumeCPU(blackholeConsumption); + return 1; + } + + @Override + protected Integer getFallback() { + return 2; + } + }; + } + + @TearDown(Level.Invocation) + public void tearDown() { + if (setUpRequestContext) { + requestContext.shutdown(); + } + } + } + + @State(Scope.Thread) + public static class ObservableCommandState { + HystrixObservableCommand command; + HystrixRequestContext requestContext; + + @Param({"true", "false"}) + public boolean forceOpen; + + @Param({"true", "false"}) + public boolean setUpRequestContext; + + //amount of "work" to give to CPU + @Param({"1", "100", "10000"}) + public int blackholeConsumption; + + @Setup(Level.Invocation) + public void setUp() { + if (setUpRequestContext) { + requestContext = HystrixRequestContext.initializeContext(); + } + + command = new HystrixObservableCommand( + HystrixObservableCommand.Setter.withGroupKey(groupKey) + .andCommandPropertiesDefaults(getCommandSetter(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE, forceOpen)) + ) { + @Override + protected Observable construct() { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + Blackhole.consumeCPU(blackholeConsumption); + return Observable.just(1); + } + }).subscribeOn(Schedulers.computation()); + } + }; + } + + @TearDown(Level.Invocation) + public void tearDown() { + if (setUpRequestContext) { + requestContext.shutdown(); + } + } + } + + @State(Scope.Benchmark) + public static class ExecutorState { + ExecutorService executorService; + + @Setup + public void setUp() { + executorService = Executors.newFixedThreadPool(100); + } + + @TearDown + public void tearDown() { + List runnables = executorService.shutdownNow(); + } + } + + @State(Scope.Benchmark) + public static class ThreadPoolState { + HystrixThreadPool hystrixThreadPool; + + @Setup + public void setUp() { + hystrixThreadPool = new HystrixThreadPool.HystrixThreadPoolDefault( + HystrixThreadPoolKey.Factory.asKey("PERF") + , HystrixThreadPoolProperties.Setter().withCoreSize(100)); + } + + @TearDown + public void tearDown() { + hystrixThreadPool.getExecutor().shutdownNow(); + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer baselineExecute(BlackholeState bhState) { + Blackhole.consumeCPU(bhState.blackholeConsumption); + return 1; + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer baselineQueue(ExecutorState state, final BlackholeState bhState) throws InterruptedException, ExecutionException { + try { + return state.executorService.submit(new Callable() { + @Override + public Integer call() throws Exception { + Blackhole.consumeCPU(bhState.blackholeConsumption); + return 1; + } + }).get(); + } catch (Throwable t) { + return 2; + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer baselineSyncObserve(final BlackholeState bhState) throws InterruptedException { + Observable syncObservable = Observable.defer(new Func0>() { + @Override + public Observable call() { + Blackhole.consumeCPU(bhState.blackholeConsumption); + return Observable.just(1); + } + }); + + try { + return syncObservable.toBlocking().first(); + } catch (Throwable t) { + return 2; + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer baselineAsyncComputationObserve(final BlackholeState bhState) throws InterruptedException { + Observable asyncObservable = Observable.defer(new Func0>() { + @Override + public Observable call() { + Blackhole.consumeCPU(bhState.blackholeConsumption); + return Observable.just(1); + } + }).subscribeOn(Schedulers.computation()); + + try { + return asyncObservable.toBlocking().first(); + } catch (Throwable t) { + return 2; + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer baselineAsyncCustomThreadPoolObserve(ThreadPoolState state, final BlackholeState bhState) { + Observable asyncObservable = Observable.defer(new Func0>() { + @Override + public Observable call() { + Blackhole.consumeCPU(bhState.blackholeConsumption); + return Observable.just(1); + } + }).subscribeOn(state.hystrixThreadPool.getScheduler()); + try { + return asyncObservable.toBlocking().first(); + } catch (Throwable t) { + return 2; + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer hystrixExecute(CommandState state) { + return state.command.execute(); + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public Integer hystrixObserve(ObservableCommandState state) { + return state.command.observe().toBlocking().first(); + } +} diff --git a/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/ObservableCollapserPerfTest.java b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/ObservableCollapserPerfTest.java new file mode 100644 index 0000000..79e1c93 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/ObservableCollapserPerfTest.java @@ -0,0 +1,231 @@ +/** + * 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.perf; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixObservableCollapser; +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesCollapserDefault; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class ObservableCollapserPerfTest { + + @State(Scope.Thread) + public static class CollapserState { + @Param({"1", "10", "100", "1000"}) + int numToCollapse; + + @Param({"1", "10", "100"}) + int numResponsesPerArg; + + @Param({"1", "1000", "1000000"}) + int blackholeConsumption; + + HystrixRequestContext reqContext; + Observable executionHandle; + + @Setup(Level.Invocation) + public void setUp() { + reqContext = HystrixRequestContext.initializeContext(); + + List> os = new ArrayList>(); + + for (int i = 0; i < numToCollapse; i++) { + TestCollapserWithMultipleResponses collapser = new TestCollapserWithMultipleResponses(i, numResponsesPerArg, blackholeConsumption); + os.add(collapser.observe()); + } + + executionHandle = Observable.merge(os); + } + + @TearDown(Level.Invocation) + public void tearDown() { + reqContext.shutdown(); + } + + } + + //TODO wire in synthetic timer + private static class TestCollapserWithMultipleResponses extends HystrixObservableCollapser { + + private final String arg; + private final static Map emitsPerArg; + private final int blackholeConsumption; + private final boolean commandConstructionFails; + private final Func1 keyMapper; + private final Action1> onMissingResponseHandler; + + static { + emitsPerArg = new HashMap(); + } + + public TestCollapserWithMultipleResponses(int arg, int numResponsePerArg, int blackholeConsumption) { + super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("COLLAPSER")).andScope(Scope.REQUEST).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withMaxRequestsInBatch(1000).withTimerDelayInMilliseconds(1))); + this.arg = arg + ""; + emitsPerArg.put(this.arg, numResponsePerArg); + this.blackholeConsumption = blackholeConsumption; + commandConstructionFails = false; + keyMapper = new Func1() { + @Override + public String call(String s) { + return s.substring(0, s.indexOf(":")); + } + }; + onMissingResponseHandler = new Action1>() { + @Override + public void call(HystrixCollapser.CollapsedRequest collapsedReq) { + collapsedReq.setResponse("missing:missing"); + } + }; + + } + + @Override + public String getRequestArgument() { + return arg; + } + + @Override + protected HystrixObservableCommand createCommand(Collection> collapsedRequests) { + if (commandConstructionFails) { + throw new RuntimeException("Exception thrown in command construction"); + } else { + List args = new ArrayList(); + + for (HystrixCollapser.CollapsedRequest collapsedRequest : collapsedRequests) { + String stringArg = collapsedRequest.getArgument(); + int intArg = Integer.parseInt(stringArg); + args.add(intArg); + } + + return new TestCollapserCommandWithMultipleResponsePerArgument(args, emitsPerArg, blackholeConsumption); + } + } + + //Data comes back in the form: 1:1, 1:2, 1:3, 2:2, 2:4, 2:6. + //This method should use the first half of that string as the request arg + @Override + protected Func1 getBatchReturnTypeKeySelector() { + return keyMapper; + + } + + @Override + protected Func1 getRequestArgumentKeySelector() { + return new Func1() { + + @Override + public String call(String s) { + return s; + } + + }; + } + + @Override + protected void onMissingResponse(HystrixCollapser.CollapsedRequest r) { + onMissingResponseHandler.call(r); + + } + + @Override + protected Func1 getBatchReturnTypeToResponseTypeMapper() { + return new Func1() { + + @Override + public String call(String s) { + return s; + } + + }; + } + } + + private static class TestCollapserCommandWithMultipleResponsePerArgument extends HystrixObservableCommand { + + private final List args; + private final Map emitsPerArg; + private final int blackholeConsumption; + + TestCollapserCommandWithMultipleResponsePerArgument(List args, Map emitsPerArg, int blackholeConsumption) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("COLLAPSER_MULTI")).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500))); + this.args = args; + this.emitsPerArg = emitsPerArg; + this.blackholeConsumption = blackholeConsumption; + } + + @Override + protected Observable construct() { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + Blackhole.consumeCPU(blackholeConsumption); + for (Integer arg: args) { + int numEmits = emitsPerArg.get(arg.toString()); + for (int j = 1; j < numEmits + 1; j++) { + subscriber.onNext(arg + ":" + (arg * j)); + } + } + } catch (Throwable ex) { + subscriber.onError(ex); + } + subscriber.onCompleted(); + } + }).subscribeOn(Schedulers.computation()); + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.SECONDS) + public List observeCollapsedAndWait(CollapserState collapserState) { + return collapserState.executionHandle.toList().toBlocking().single(); + } +} diff --git a/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingMaxPerfTest.java b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingMaxPerfTest.java new file mode 100644 index 0000000..837c1f5 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingMaxPerfTest.java @@ -0,0 +1,132 @@ +/** + * 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.perf; + +import com.netflix.hystrix.util.HystrixRollingNumber; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Group; +import org.openjdk.jmh.annotations.GroupThreads; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class RollingMaxPerfTest { + @State(Scope.Thread) + public static class CounterState { + HystrixRollingNumber counter; + + @Setup(Level.Iteration) + public void setUp() { + counter = new HystrixRollingNumber(100, 10); + } + } + + @State(Scope.Thread) + public static class ValueState { + final Random r = new Random(); + + int value; + + @Setup(Level.Invocation) + public void setUp() { + value = r.nextInt(100); + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingNumber writeOnly(CounterState counterState, ValueState valueState) { + counterState.counter.updateRollingMax(HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE, valueState.value); + return counterState.counter; + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long readOnly(CounterState counterState) { + HystrixRollingNumber counter = counterState.counter; + return counter.getRollingMaxValue(HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE); + } + + @Benchmark + @Group("writeHeavy") + @GroupThreads(7) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingNumber writeHeavyUpdateMax(CounterState counterState, ValueState valueState) { + counterState.counter.updateRollingMax(HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE, valueState.value); + return counterState.counter; + } + + @Benchmark + @Group("writeHeavy") + @GroupThreads(1) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long writeHeavyReadMetrics(CounterState counterState) { + HystrixRollingNumber counter = counterState.counter; + return counter.getRollingMaxValue(HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE); + } + + @Benchmark + @Group("evenSplit") + @GroupThreads(4) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingNumber evenSplitUpdateMax(CounterState counterState, ValueState valueState) { + counterState.counter.updateRollingMax(HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE, valueState.value); + return counterState.counter; + } + + @Benchmark + @Group("evenSplit") + @GroupThreads(4) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long evenSplitReadMetrics(CounterState counterState) { + HystrixRollingNumber counter = counterState.counter; + return counter.getRollingMaxValue(HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE); + } + + @Benchmark + @Group("readHeavy") + @GroupThreads(1) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingNumber readHeavyUpdateMax(CounterState counterState, ValueState valueState) { + counterState.counter.updateRollingMax(HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE, valueState.value); + return counterState.counter; + } + + @Benchmark + @Group("readHeavy") + @GroupThreads(7) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long readHeavyReadMetrics(CounterState counterState) { + HystrixRollingNumber counter = counterState.counter; + return counter.getRollingMaxValue(HystrixRollingNumberEvent.COMMAND_MAX_ACTIVE); + } +} diff --git a/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingNumberPerfTest.java b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingNumberPerfTest.java new file mode 100644 index 0000000..83882d1 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingNumberPerfTest.java @@ -0,0 +1,166 @@ +/** + * 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.perf; + +import com.netflix.hystrix.util.HystrixRollingNumber; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Group; +import org.openjdk.jmh.annotations.GroupThreads; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class RollingNumberPerfTest { + @State(Scope.Thread) + public static class CounterState { + HystrixRollingNumber counter; + + @Setup(Level.Iteration) + public void setUp() { + counter = new HystrixRollingNumber(100, 10); + } + } + + @State(Scope.Thread) + public static class ValueState { + final Random r = new Random(); + + int value; + HystrixRollingNumberEvent type; + + @Setup(Level.Invocation) + public void setUp() { + value = r.nextInt(100); + int typeInt = r.nextInt(3); + switch(typeInt) { + case 0: + type = HystrixRollingNumberEvent.SUCCESS; + break; + case 1: + type = HystrixRollingNumberEvent.FAILURE; + break; + case 2: + type = HystrixRollingNumberEvent.TIMEOUT; + break; + default: throw new RuntimeException("Unexpected : " + typeInt); + } + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingNumber writeOnly(CounterState counterState, ValueState valueState) { + counterState.counter.add(valueState.type, valueState.value); + return counterState.counter; + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long readOnly(CounterState counterState) { + HystrixRollingNumber counter = counterState.counter; + return counter.getCumulativeSum(HystrixRollingNumberEvent.SUCCESS) + + counter.getCumulativeSum(HystrixRollingNumberEvent.FAILURE) + + counter.getCumulativeSum(HystrixRollingNumberEvent.TIMEOUT) + + counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS) + + counter.getRollingSum(HystrixRollingNumberEvent.FAILURE) + + counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT); + } + + @Benchmark + @Group("writeHeavy") + @GroupThreads(7) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingNumber writeHeavyCounterAdd(CounterState counterState, ValueState valueState) { + counterState.counter.add(valueState.type, valueState.value); + return counterState.counter; + } + + @Benchmark + @Group("writeHeavy") + @GroupThreads(1) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long writeHeavyReadMetrics(CounterState counterState) { + HystrixRollingNumber counter = counterState.counter; + return counter.getCumulativeSum(HystrixRollingNumberEvent.SUCCESS) + + counter.getCumulativeSum(HystrixRollingNumberEvent.FAILURE) + + counter.getCumulativeSum(HystrixRollingNumberEvent.TIMEOUT) + + counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS) + + counter.getRollingSum(HystrixRollingNumberEvent.FAILURE) + + counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT); + } + + @Benchmark + @Group("evenSplit") + @GroupThreads(4) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingNumber evenSplitCounterAdd(CounterState counterState, ValueState valueState) { + counterState.counter.add(valueState.type, valueState.value); + return counterState.counter; + } + + @Benchmark + @Group("evenSplit") + @GroupThreads(4) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long evenSplitReadMetrics(CounterState counterState) { + HystrixRollingNumber counter = counterState.counter; + return counter.getCumulativeSum(HystrixRollingNumberEvent.SUCCESS) + + counter.getCumulativeSum(HystrixRollingNumberEvent.FAILURE) + + counter.getCumulativeSum(HystrixRollingNumberEvent.TIMEOUT) + + counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS) + + counter.getRollingSum(HystrixRollingNumberEvent.FAILURE) + + counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT); + } + + @Benchmark + @Group("readHeavy") + @GroupThreads(1) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingNumber readHeavyCounterAdd(CounterState counterState, ValueState valueState) { + counterState.counter.add(valueState.type, valueState.value); + return counterState.counter; + } + + @Benchmark + @Group("readHeavy") + @GroupThreads(7) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long readHeavyReadMetrics(CounterState counterState) { + HystrixRollingNumber counter = counterState.counter; + return counter.getCumulativeSum(HystrixRollingNumberEvent.SUCCESS) + + counter.getCumulativeSum(HystrixRollingNumberEvent.FAILURE) + + counter.getCumulativeSum(HystrixRollingNumberEvent.TIMEOUT) + + counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS) + + counter.getRollingSum(HystrixRollingNumberEvent.FAILURE) + + counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT); + } +} diff --git a/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingPercentilePerfTest.java b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingPercentilePerfTest.java new file mode 100644 index 0000000..a3719d2 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/jmh/java/com/netflix/hystrix/perf/RollingPercentilePerfTest.java @@ -0,0 +1,168 @@ +/** + * 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.perf; + +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.HystrixRollingPercentile; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Group; +import org.openjdk.jmh.annotations.GroupThreads; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class RollingPercentilePerfTest { + @State(Scope.Thread) + public static class PercentileState { + HystrixRollingPercentile percentile; + + @Param({"true", "false"}) + public boolean percentileEnabled; + + @Setup(Level.Iteration) + public void setUp() { + percentile = new HystrixRollingPercentile(100, 10, 1000, HystrixProperty.Factory.asProperty(percentileEnabled)); + } + } + + @State(Scope.Thread) + public static class LatencyState { + final Random r = new Random(); + + int latency; + + @Setup(Level.Invocation) + public void setUp() { + latency = r.nextInt(100); + } + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingPercentile writeOnly(PercentileState percentileState, LatencyState latencyState) { + percentileState.percentile.addValue(latencyState.latency); + return percentileState.percentile; + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public int readOnly(PercentileState percentileState) { + HystrixRollingPercentile percentile = percentileState.percentile; + return percentile.getMean() + + percentile.getPercentile(10) + + percentile.getPercentile(25) + + percentile.getPercentile(50) + + percentile.getPercentile(75) + + percentile.getPercentile(90) + + percentile.getPercentile(95) + + percentile.getPercentile(99) + + percentile.getPercentile(99.5); + } + + @Benchmark + @Group("writeHeavy") + @GroupThreads(7) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingPercentile writeHeavyLatencyAdd(PercentileState percentileState, LatencyState latencyState) { + percentileState.percentile.addValue(latencyState.latency); + return percentileState.percentile; + } + + @Benchmark + @Group("writeHeavy") + @GroupThreads(1) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public int writeHeavyReadMetrics(PercentileState percentileState) { + HystrixRollingPercentile percentile = percentileState.percentile; + return percentile.getMean() + + percentile.getPercentile(10) + + percentile.getPercentile(25) + + percentile.getPercentile(50) + + percentile.getPercentile(75) + + percentile.getPercentile(90) + + percentile.getPercentile(95) + + percentile.getPercentile(99) + + percentile.getPercentile(99.5); + } + + @Benchmark + @Group("evenSplit") + @GroupThreads(4) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingPercentile evenSplitLatencyAdd(PercentileState percentileState, LatencyState latencyState) { + percentileState.percentile.addValue(latencyState.latency); + return percentileState.percentile; + } + + @Benchmark + @Group("evenSplit") + @GroupThreads(4) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public int evenSplitReadMetrics(PercentileState percentileState) { + HystrixRollingPercentile percentile = percentileState.percentile; + return percentile.getMean() + + percentile.getPercentile(10) + + percentile.getPercentile(25) + + percentile.getPercentile(50) + + percentile.getPercentile(75) + + percentile.getPercentile(90) + + percentile.getPercentile(95) + + percentile.getPercentile(99) + + percentile.getPercentile(99.5); + } + + @Benchmark + @Group("readHeavy") + @GroupThreads(1) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public HystrixRollingPercentile readHeavyLatencyAdd(PercentileState percentileState, LatencyState latencyState) { + percentileState.percentile.addValue(latencyState.latency); + return percentileState.percentile; + } + + @Benchmark + @Group("readHeavy") + @GroupThreads(7) + @BenchmarkMode({Mode.Throughput}) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public int readHeavyReadMetrics(PercentileState percentileState) { + HystrixRollingPercentile percentile = percentileState.percentile; + return percentile.getMean() + + percentile.getPercentile(10) + + percentile.getPercentile(25) + + percentile.getPercentile(50) + + percentile.getPercentile(75) + + percentile.getPercentile(90) + + percentile.getPercentile(95) + + percentile.getPercentile(99) + + percentile.getPercentile(99.5); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java new file mode 100644 index 0000000..ad1f654 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java @@ -0,0 +1,2219 @@ +/** + * Copyright 2013 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 com.netflix.hystrix.HystrixCircuitBreaker.NoOpCircuitBreaker; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.exception.ExceptionNotWrappedByHystrix; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; +import com.netflix.hystrix.exception.HystrixTimeoutException; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherFactory; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.HystrixTimer; +import com.netflix.hystrix.util.HystrixTimer.TimerListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Notification; +import rx.Observable; +import rx.Observable.Operator; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.subjects.ReplaySubject; +import rx.subscriptions.CompositeSubscription; + +import java.lang.ref.Reference; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/* package */abstract class AbstractCommand implements HystrixInvokableInfo, HystrixObservable { + private static final Logger logger = LoggerFactory.getLogger(AbstractCommand.class); + protected final HystrixCircuitBreaker circuitBreaker; + protected final HystrixThreadPool threadPool; + protected final HystrixThreadPoolKey threadPoolKey; + protected final HystrixCommandProperties properties; + + protected enum TimedOutStatus { + NOT_EXECUTED, COMPLETED, TIMED_OUT + } + + protected enum CommandState { + NOT_STARTED, OBSERVABLE_CHAIN_CREATED, USER_CODE_EXECUTED, UNSUBSCRIBED, TERMINAL + } + + protected enum ThreadState { + NOT_USING_THREAD, STARTED, UNSUBSCRIBED, TERMINAL + } + + protected final HystrixCommandMetrics metrics; + + protected final HystrixCommandKey commandKey; + protected final HystrixCommandGroupKey commandGroup; + + /** + * Plugin implementations + */ + protected final HystrixEventNotifier eventNotifier; + protected final HystrixConcurrencyStrategy concurrencyStrategy; + protected final HystrixCommandExecutionHook executionHook; + + /* FALLBACK Semaphore */ + protected final TryableSemaphore fallbackSemaphoreOverride; + /* each circuit has a semaphore to restrict concurrent fallback execution */ + protected static final ConcurrentHashMap fallbackSemaphorePerCircuit = new ConcurrentHashMap(); + /* END FALLBACK Semaphore */ + + /* EXECUTION Semaphore */ + protected final TryableSemaphore executionSemaphoreOverride; + /* each circuit has a semaphore to restrict concurrent fallback execution */ + protected static final ConcurrentHashMap executionSemaphorePerCircuit = new ConcurrentHashMap(); + /* END EXECUTION Semaphore */ + + protected final AtomicReference> timeoutTimer = new AtomicReference>(); + + protected AtomicReference commandState = new AtomicReference(CommandState.NOT_STARTED); + protected AtomicReference threadState = new AtomicReference(ThreadState.NOT_USING_THREAD); + + /* + * {@link ExecutionResult} refers to what happened as the user-provided code ran. If request-caching is used, + * then multiple command instances will have a reference to the same {@link ExecutionResult}. So all values there + * should be the same, even in the presence of request-caching. + * + * If some values are not properly shareable, then they belong on the command instance, so they are not visible to + * other commands. + * + * Examples: RESPONSE_FROM_CACHE, CANCELLED HystrixEventTypes + */ + protected volatile ExecutionResult executionResult = ExecutionResult.EMPTY; //state on shared execution + + protected volatile boolean isResponseFromCache = false; + protected volatile ExecutionResult executionResultAtTimeOfCancellation; + protected volatile long commandStartTimestamp = -1L; + + /* If this command executed and timed-out */ + protected final AtomicReference isCommandTimedOut = new AtomicReference(TimedOutStatus.NOT_EXECUTED); + protected volatile Action0 endCurrentThreadExecutingCommand; + + /** + * Instance of RequestCache logic + */ + protected final HystrixRequestCache requestCache; + protected final HystrixRequestLog currentRequestLog; + + // this is a micro-optimization but saves about 1-2microseconds (on 2011 MacBook Pro) + // on the repetitive string processing that will occur on the same classes over and over again + private static ConcurrentHashMap, String> defaultNameCache = new ConcurrentHashMap, String>(); + + protected static ConcurrentHashMap commandContainsFallback = new ConcurrentHashMap(); + + /* package */static String getDefaultNameFromClass(Class cls) { + String fromCache = defaultNameCache.get(cls); + if (fromCache != null) { + return fromCache; + } + // generate the default + // default HystrixCommandKey to use if the method is not overridden + String name = cls.getSimpleName(); + if (name.equals("")) { + // we don't have a SimpleName (anonymous inner class) so use the full class name + name = cls.getName(); + name = name.substring(name.lastIndexOf('.') + 1, name.length()); + } + defaultNameCache.put(cls, name); + return name; + } + + protected AbstractCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, + HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults, + HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore, + HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { + + this.commandGroup = initGroupKey(group); + this.commandKey = initCommandKey(key, getClass()); + this.properties = initCommandProperties(this.commandKey, propertiesStrategy, commandPropertiesDefaults); + this.threadPoolKey = initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get()); + this.metrics = initMetrics(metrics, this.commandGroup, this.threadPoolKey, this.commandKey, this.properties); + this.circuitBreaker = initCircuitBreaker(this.properties.circuitBreakerEnabled().get(), circuitBreaker, this.commandGroup, this.commandKey, this.properties, this.metrics); + this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults); + + //Strategies from plugins + this.eventNotifier = HystrixPlugins.getInstance().getEventNotifier(); + this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy(); + HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(this.commandKey, this.commandGroup, this.metrics, this.circuitBreaker, this.properties); + this.executionHook = initExecutionHook(executionHook); + + this.requestCache = HystrixRequestCache.getInstance(this.commandKey, this.concurrencyStrategy); + this.currentRequestLog = initRequestLog(this.properties.requestLogEnabled().get(), this.concurrencyStrategy); + + /* fallback semaphore override if applicable */ + this.fallbackSemaphoreOverride = fallbackSemaphore; + + /* execution semaphore override if applicable */ + this.executionSemaphoreOverride = executionSemaphore; + } + + private static HystrixCommandGroupKey initGroupKey(final HystrixCommandGroupKey fromConstructor) { + if (fromConstructor == null) { + throw new IllegalStateException("HystrixCommandGroup can not be NULL"); + } else { + return fromConstructor; + } + } + + private static HystrixCommandKey initCommandKey(final HystrixCommandKey fromConstructor, Class clazz) { + if (fromConstructor == null || fromConstructor.name().trim().equals("")) { + final String keyName = getDefaultNameFromClass(clazz); + return HystrixCommandKey.Factory.asKey(keyName); + } else { + return fromConstructor; + } + } + + private static HystrixCommandProperties initCommandProperties(HystrixCommandKey commandKey, HystrixPropertiesStrategy propertiesStrategy, HystrixCommandProperties.Setter commandPropertiesDefaults) { + if (propertiesStrategy == null) { + return HystrixPropertiesFactory.getCommandProperties(commandKey, commandPropertiesDefaults); + } else { + // used for unit testing + return propertiesStrategy.getCommandProperties(commandKey, commandPropertiesDefaults); + } + } + + /* + * ThreadPoolKey + * + * This defines which thread-pool this command should run on. + * + * It uses the HystrixThreadPoolKey if provided, then defaults to use HystrixCommandGroup. + * + * It can then be overridden by a property if defined so it can be changed at runtime. + */ + private static HystrixThreadPoolKey initThreadPoolKey(HystrixThreadPoolKey threadPoolKey, HystrixCommandGroupKey groupKey, String threadPoolKeyOverride) { + if (threadPoolKeyOverride == null) { + // we don't have a property overriding the value so use either HystrixThreadPoolKey or HystrixCommandGroup + if (threadPoolKey == null) { + /* use HystrixCommandGroup if HystrixThreadPoolKey is null */ + return HystrixThreadPoolKey.Factory.asKey(groupKey.name()); + } else { + return threadPoolKey; + } + } else { + // we have a property defining the thread-pool so use it instead + return HystrixThreadPoolKey.Factory.asKey(threadPoolKeyOverride); + } + } + + private static HystrixCommandMetrics initMetrics(HystrixCommandMetrics fromConstructor, HystrixCommandGroupKey groupKey, + HystrixThreadPoolKey threadPoolKey, HystrixCommandKey commandKey, + HystrixCommandProperties properties) { + if (fromConstructor == null) { + return HystrixCommandMetrics.getInstance(commandKey, groupKey, threadPoolKey, properties); + } else { + return fromConstructor; + } + } + + private static HystrixCircuitBreaker initCircuitBreaker(boolean enabled, HystrixCircuitBreaker fromConstructor, + HystrixCommandGroupKey groupKey, HystrixCommandKey commandKey, + HystrixCommandProperties properties, HystrixCommandMetrics metrics) { + if (enabled) { + if (fromConstructor == null) { + // get the default implementation of HystrixCircuitBreaker + return HystrixCircuitBreaker.Factory.getInstance(commandKey, groupKey, properties, metrics); + } else { + return fromConstructor; + } + } else { + return new NoOpCircuitBreaker(); + } + } + + private static HystrixCommandExecutionHook initExecutionHook(HystrixCommandExecutionHook fromConstructor) { + if (fromConstructor == null) { + return new ExecutionHookDeprecationWrapper(HystrixPlugins.getInstance().getCommandExecutionHook()); + } else { + // used for unit testing + if (fromConstructor instanceof ExecutionHookDeprecationWrapper) { + return fromConstructor; + } else { + return new ExecutionHookDeprecationWrapper(fromConstructor); + } + } + } + + private static HystrixThreadPool initThreadPool(HystrixThreadPool fromConstructor, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) { + if (fromConstructor == null) { + // get the default implementation of HystrixThreadPool + return HystrixThreadPool.Factory.getInstance(threadPoolKey, threadPoolPropertiesDefaults); + } else { + return fromConstructor; + } + } + + private static HystrixRequestLog initRequestLog(boolean enabled, HystrixConcurrencyStrategy concurrencyStrategy) { + if (enabled) { + /* store reference to request log regardless of which thread later hits it */ + return HystrixRequestLog.getCurrentRequest(concurrencyStrategy); + } else { + return null; + } + } + + /** + * Allow the Collapser to mark this command instance as being used for a collapsed request and how many requests were collapsed. + * + * @param sizeOfBatch number of commands in request batch + */ + /* package */void markAsCollapsedCommand(HystrixCollapserKey collapserKey, int sizeOfBatch) { + eventNotifier.markEvent(HystrixEventType.COLLAPSED, this.commandKey); + executionResult = executionResult.markCollapsed(collapserKey, sizeOfBatch); + } + + /** + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. + *

+ * This eagerly starts execution of the command the same as {@link HystrixCommand#queue()} and {@link HystrixCommand#execute()}. + *

+ * A lazy {@link Observable} can be obtained from {@link #toObservable()}. + *

+ * See https://github.com/Netflix/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of command execution or a fallback if the command fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public Observable observe() { + // us a ReplaySubject to buffer the eagerly subscribed-to Observable + ReplaySubject subject = ReplaySubject.create(); + // eagerly kick off subscription + final Subscription sourceSubscription = toObservable().subscribe(subject); + // return the subject that can be subscribed to later while the execution has already started + return subject.doOnUnsubscribe(new Action0() { + @Override + public void call() { + sourceSubscription.unsubscribe(); + } + }); + } + + protected abstract Observable getExecutionObservable(); + + protected abstract Observable getFallbackObservable(); + + /** + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. + *

+ * This lazily starts execution of the command once the {@link Observable} is subscribed to. + *

+ * An eager {@link Observable} can be obtained from {@link #observe()}. + *

+ * See https://github.com/ReactiveX/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of command execution or a fallback if the command fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public Observable toObservable() { + final AbstractCommand _cmd = this; + + //doOnCompleted handler already did all of the SUCCESS work + //doOnError handler already did all of the FAILURE/TIMEOUT/REJECTION/BAD_REQUEST work + final Action0 terminateCommandCleanup = new Action0() { + + @Override + public void call() { + if (_cmd.commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.TERMINAL)) { + handleCommandEnd(false); //user code never ran + } else if (_cmd.commandState.compareAndSet(CommandState.USER_CODE_EXECUTED, CommandState.TERMINAL)) { + handleCommandEnd(true); //user code did run + } + } + }; + + //mark the command as CANCELLED and store the latency (in addition to standard cleanup) + final Action0 unsubscribeCommandCleanup = new Action0() { + @Override + public void call() { + circuitBreaker.markNonSuccess(); + if (_cmd.commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.UNSUBSCRIBED)) { + if (!_cmd.executionResult.containsTerminalEvent()) { + _cmd.eventNotifier.markEvent(HystrixEventType.CANCELLED, _cmd.commandKey); + try { + executionHook.onUnsubscribe(_cmd); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onUnsubscribe", hookEx); + } + _cmd.executionResultAtTimeOfCancellation = _cmd.executionResult + .addEvent((int) (System.currentTimeMillis() - _cmd.commandStartTimestamp), HystrixEventType.CANCELLED); + } + handleCommandEnd(false); //user code never ran + } else if (_cmd.commandState.compareAndSet(CommandState.USER_CODE_EXECUTED, CommandState.UNSUBSCRIBED)) { + if (!_cmd.executionResult.containsTerminalEvent()) { + _cmd.eventNotifier.markEvent(HystrixEventType.CANCELLED, _cmd.commandKey); + try { + executionHook.onUnsubscribe(_cmd); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onUnsubscribe", hookEx); + } + _cmd.executionResultAtTimeOfCancellation = _cmd.executionResult + .addEvent((int) (System.currentTimeMillis() - _cmd.commandStartTimestamp), HystrixEventType.CANCELLED); + } + handleCommandEnd(true); //user code did run + } + } + }; + + final Func0> applyHystrixSemantics = new Func0>() { + @Override + public Observable call() { + if (commandState.get().equals(CommandState.UNSUBSCRIBED)) { + return Observable.never(); + } + return applyHystrixSemantics(_cmd); + } + }; + + final Func1 wrapWithAllOnNextHooks = new Func1() { + @Override + public R call(R r) { + R afterFirstApplication = r; + + try { + afterFirstApplication = executionHook.onComplete(_cmd, r); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onComplete", hookEx); + } + + try { + return executionHook.onEmit(_cmd, afterFirstApplication); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onEmit", hookEx); + return afterFirstApplication; + } + } + }; + + final Action0 fireOnCompletedHook = new Action0() { + @Override + public void call() { + try { + executionHook.onSuccess(_cmd); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onSuccess", hookEx); + } + } + }; + + return Observable.defer(new Func0>() { + @Override + public Observable call() { + /* this is a stateful object so can only be used once */ + if (!commandState.compareAndSet(CommandState.NOT_STARTED, CommandState.OBSERVABLE_CHAIN_CREATED)) { + IllegalStateException ex = new IllegalStateException("This instance can only be executed once. Please instantiate a new instance."); + //TODO make a new error type for this + throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, _cmd.getClass(), getLogMessagePrefix() + " command executed multiple times - this is not permitted.", ex, null); + } + + commandStartTimestamp = System.currentTimeMillis(); + + if (properties.requestLogEnabled().get()) { + // log this command execution regardless of what happened + if (currentRequestLog != null) { + currentRequestLog.addExecutedCommand(_cmd); + } + } + + final boolean requestCacheEnabled = isRequestCachingEnabled(); + final String cacheKey = getCacheKey(); + + /* try from cache first */ + if (requestCacheEnabled) { + HystrixCommandResponseFromCache fromCache = (HystrixCommandResponseFromCache) requestCache.get(cacheKey); + if (fromCache != null) { + isResponseFromCache = true; + return handleRequestCacheHitAndEmitValues(fromCache, _cmd); + } + } + + Observable hystrixObservable = + Observable.defer(applyHystrixSemantics) + .map(wrapWithAllOnNextHooks); + + Observable afterCache; + + // put in cache + if (requestCacheEnabled && cacheKey != null) { + // wrap it for caching + HystrixCachedObservable toCache = HystrixCachedObservable.from(hystrixObservable, _cmd); + HystrixCommandResponseFromCache fromCache = (HystrixCommandResponseFromCache) requestCache.putIfAbsent(cacheKey, toCache); + if (fromCache != null) { + // another thread beat us so we'll use the cached value instead + toCache.unsubscribe(); + isResponseFromCache = true; + return handleRequestCacheHitAndEmitValues(fromCache, _cmd); + } else { + // we just created an ObservableCommand so we cast and return it + afterCache = toCache.toObservable(); + } + } else { + afterCache = hystrixObservable; + } + + return afterCache + .doOnTerminate(terminateCommandCleanup) // perform cleanup once (either on normal terminal state (this line), or unsubscribe (next line)) + .doOnUnsubscribe(unsubscribeCommandCleanup) // perform cleanup once + .doOnCompleted(fireOnCompletedHook); + } + }); + } + + private Observable applyHystrixSemantics(final AbstractCommand _cmd) { + // mark that we're starting execution on the ExecutionHook + // if this hook throws an exception, then a fast-fail occurs with no fallback. No state is left inconsistent + executionHook.onStart(_cmd); + + /* determine if we're allowed to execute */ + if (circuitBreaker.attemptExecution()) { + final TryableSemaphore executionSemaphore = getExecutionSemaphore(); + final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false); + final Action0 singleSemaphoreRelease = new Action0() { + @Override + public void call() { + if (semaphoreHasBeenReleased.compareAndSet(false, true)) { + executionSemaphore.release(); + } + } + }; + + final Action1 markExceptionThrown = new Action1() { + @Override + public void call(Throwable t) { + eventNotifier.markEvent(HystrixEventType.EXCEPTION_THROWN, commandKey); + } + }; + + if (executionSemaphore.tryAcquire()) { + try { + /* used to track userThreadExecutionTime */ + executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis()); + return executeCommandAndObserve(_cmd) + .doOnError(markExceptionThrown) + .doOnTerminate(singleSemaphoreRelease) + .doOnUnsubscribe(singleSemaphoreRelease); + } catch (RuntimeException e) { + return Observable.error(e); + } + } else { + return handleSemaphoreRejectionViaFallback(); + } + } else { + return handleShortCircuitViaFallback(); + } + } + + abstract protected boolean commandIsScalar(); + + /** + * This decorates "Hystrix" functionality around the run() Observable. + * + * @return R + */ + private Observable executeCommandAndObserve(final AbstractCommand _cmd) { + final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread(); + + final Action1 markEmits = new Action1() { + @Override + public void call(R r) { + if (shouldOutputOnNextEvents()) { + executionResult = executionResult.addEvent(HystrixEventType.EMIT); + eventNotifier.markEvent(HystrixEventType.EMIT, commandKey); + } + if (commandIsScalar()) { + long latency = System.currentTimeMillis() - executionResult.getStartTimestamp(); + eventNotifier.markEvent(HystrixEventType.SUCCESS, commandKey); + executionResult = executionResult.addEvent((int) latency, HystrixEventType.SUCCESS); + eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(), (int) latency, executionResult.getOrderedList()); + circuitBreaker.markSuccess(); + } + } + }; + + final Action0 markOnCompleted = new Action0() { + @Override + public void call() { + if (!commandIsScalar()) { + long latency = System.currentTimeMillis() - executionResult.getStartTimestamp(); + eventNotifier.markEvent(HystrixEventType.SUCCESS, commandKey); + executionResult = executionResult.addEvent((int) latency, HystrixEventType.SUCCESS); + eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(), (int) latency, executionResult.getOrderedList()); + circuitBreaker.markSuccess(); + } + } + }; + + final Func1> handleFallback = new Func1>() { + @Override + public Observable call(Throwable t) { + circuitBreaker.markNonSuccess(); + Exception e = getExceptionFromThrowable(t); + executionResult = executionResult.setExecutionException(e); + if (e instanceof RejectedExecutionException) { + return handleThreadPoolRejectionViaFallback(e); + } else if (t instanceof HystrixTimeoutException) { + return handleTimeoutViaFallback(); + } else if (t instanceof HystrixBadRequestException) { + return handleBadRequestByEmittingError(e); + } else { + /* + * Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException. + */ + if (e instanceof HystrixBadRequestException) { + eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey); + return Observable.error(e); + } + + return handleFailureViaFallback(e); + } + } + }; + + final Action1> setRequestContext = new Action1>() { + @Override + public void call(Notification rNotification) { + setRequestContextIfNeeded(currentRequestContext); + } + }; + + Observable execution; + if (properties.executionTimeoutEnabled().get()) { + execution = executeCommandWithSpecifiedIsolation(_cmd) + .lift(new HystrixObservableTimeoutOperator(_cmd)); + } else { + execution = executeCommandWithSpecifiedIsolation(_cmd); + } + + return execution.doOnNext(markEmits) + .doOnCompleted(markOnCompleted) + .onErrorResumeNext(handleFallback) + .doOnEach(setRequestContext); + } + + private Observable executeCommandWithSpecifiedIsolation(final AbstractCommand _cmd) { + if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) { + // mark that we are executing in a thread (even if we end up being rejected we still were a THREAD execution and not SEMAPHORE) + return Observable.defer(new Func0>() { + @Override + public Observable call() { + executionResult = executionResult.setExecutionOccurred(); + if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) { + return Observable.error(new IllegalStateException("execution attempted while in state : " + commandState.get().name())); + } + + metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.THREAD); + + if (isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT) { + // the command timed out in the wrapping thread so we will return immediately + // and not increment any of the counters below or other such logic + return Observable.error(new RuntimeException("timed out before executing run()")); + } + if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.STARTED)) { + //we have not been unsubscribed, so should proceed + HystrixCounters.incrementGlobalConcurrentThreads(); + threadPool.markThreadExecution(); + // store the command that is being run + endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey()); + executionResult = executionResult.setExecutedInThread(); + /** + * If any of these hooks throw an exception, then it appears as if the actual execution threw an error + */ + try { + executionHook.onThreadStart(_cmd); + executionHook.onRunStart(_cmd); + executionHook.onExecutionStart(_cmd); + return getUserExecutionObservable(_cmd); + } catch (Throwable ex) { + return Observable.error(ex); + } + } else { + //command has already been unsubscribed, so return immediately + return Observable.empty(); + } + } + }).doOnTerminate(new Action0() { + @Override + public void call() { + if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.TERMINAL)) { + handleThreadEnd(_cmd); + } + if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.TERMINAL)) { + //if it was never started and received terminal, then no need to clean up (I don't think this is possible) + } + //if it was unsubscribed, then other cleanup handled it + } + }).doOnUnsubscribe(new Action0() { + @Override + public void call() { + if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.UNSUBSCRIBED)) { + handleThreadEnd(_cmd); + } + if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.UNSUBSCRIBED)) { + //if it was never started and was cancelled, then no need to clean up + } + //if it was terminal, then other cleanup handled it + } + }).subscribeOn(threadPool.getScheduler(new Func0() { + @Override + public Boolean call() { + return properties.executionIsolationThreadInterruptOnTimeout().get() && _cmd.isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT; + } + })); + } else { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + executionResult = executionResult.setExecutionOccurred(); + if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) { + return Observable.error(new IllegalStateException("execution attempted while in state : " + commandState.get().name())); + } + + metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.SEMAPHORE); + // semaphore isolated + // store the command that is being run + endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey()); + try { + executionHook.onRunStart(_cmd); + executionHook.onExecutionStart(_cmd); + return getUserExecutionObservable(_cmd); //the getUserExecutionObservable method already wraps sync exceptions, so this shouldn't throw + } catch (Throwable ex) { + //If the above hooks throw, then use that as the result of the run method + return Observable.error(ex); + } + } + }); + } + } + + /** + * Execute getFallback() within protection of a semaphore that limits number of concurrent executions. + *

+ * Fallback implementations shouldn't perform anything that can be blocking, but we protect against it anyways in case someone doesn't abide by the contract. + *

+ * If something in the getFallback() implementation is latent (such as a network call) then the semaphore will cause us to start rejecting requests rather than allowing potentially + * all threads to pile up and block. + * + * @return K + * @throws UnsupportedOperationException + * if getFallback() not implemented + * @throws HystrixRuntimeException + * if getFallback() fails (throws an Exception) or is rejected by the semaphore + */ + private Observable getFallbackOrThrowException(final AbstractCommand _cmd, final HystrixEventType eventType, final FailureType failureType, final String message, final Exception originalException) { + final HystrixRequestContext requestContext = HystrixRequestContext.getContextForCurrentThread(); + long latency = System.currentTimeMillis() - executionResult.getStartTimestamp(); + // record the executionResult + // do this before executing fallback so it can be queried from within getFallback (see See https://github.com/Netflix/Hystrix/pull/144) + executionResult = executionResult.addEvent((int) latency, eventType); + + if (isUnrecoverable(originalException)) { + logger.error("Unrecoverable Error for HystrixCommand so will throw HystrixRuntimeException and not apply fallback. ", originalException); + + /* executionHook for all errors */ + Exception e = wrapWithOnErrorHook(failureType, originalException); + return Observable.error(new HystrixRuntimeException(failureType, this.getClass(), getLogMessagePrefix() + " " + message + " and encountered unrecoverable error.", e, null)); + } else { + if (isRecoverableError(originalException)) { + logger.warn("Recovered from java.lang.Error by serving Hystrix fallback", originalException); + } + + if (properties.fallbackEnabled().get()) { + /* fallback behavior is permitted so attempt */ + + final Action1> setRequestContext = new Action1>() { + @Override + public void call(Notification rNotification) { + setRequestContextIfNeeded(requestContext); + } + }; + + final Action1 markFallbackEmit = new Action1() { + @Override + public void call(R r) { + if (shouldOutputOnNextEvents()) { + executionResult = executionResult.addEvent(HystrixEventType.FALLBACK_EMIT); + eventNotifier.markEvent(HystrixEventType.FALLBACK_EMIT, commandKey); + } + } + }; + + final Action0 markFallbackCompleted = new Action0() { + @Override + public void call() { + long latency = System.currentTimeMillis() - executionResult.getStartTimestamp(); + eventNotifier.markEvent(HystrixEventType.FALLBACK_SUCCESS, commandKey); + executionResult = executionResult.addEvent((int) latency, HystrixEventType.FALLBACK_SUCCESS); + } + }; + + final Func1> handleFallbackError = new Func1>() { + @Override + public Observable call(Throwable t) { + /* executionHook for all errors */ + Exception e = wrapWithOnErrorHook(failureType, originalException); + Exception fe = getExceptionFromThrowable(t); + + long latency = System.currentTimeMillis() - executionResult.getStartTimestamp(); + Exception toEmit; + + if (fe instanceof UnsupportedOperationException) { + logger.debug("No fallback for HystrixCommand. ", fe); // debug only since we're throwing the exception and someone higher will do something with it + eventNotifier.markEvent(HystrixEventType.FALLBACK_MISSING, commandKey); + executionResult = executionResult.addEvent((int) latency, HystrixEventType.FALLBACK_MISSING); + + toEmit = new HystrixRuntimeException(failureType, _cmd.getClass(), getLogMessagePrefix() + " " + message + " and no fallback available.", e, fe); + } else { + logger.debug("HystrixCommand execution " + failureType.name() + " and fallback failed.", fe); + eventNotifier.markEvent(HystrixEventType.FALLBACK_FAILURE, commandKey); + executionResult = executionResult.addEvent((int) latency, HystrixEventType.FALLBACK_FAILURE); + + toEmit = new HystrixRuntimeException(failureType, _cmd.getClass(), getLogMessagePrefix() + " " + message + " and fallback failed.", e, fe); + } + + // NOTE: we're suppressing fallback exception here + if (shouldNotBeWrapped(originalException)) { + return Observable.error(e); + } + + return Observable.error(toEmit); + } + }; + + final TryableSemaphore fallbackSemaphore = getFallbackSemaphore(); + final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false); + final Action0 singleSemaphoreRelease = new Action0() { + @Override + public void call() { + if (semaphoreHasBeenReleased.compareAndSet(false, true)) { + fallbackSemaphore.release(); + } + } + }; + + Observable fallbackExecutionChain; + + // acquire a permit + if (fallbackSemaphore.tryAcquire()) { + try { + if (isFallbackUserDefined()) { + executionHook.onFallbackStart(this); + fallbackExecutionChain = getFallbackObservable(); + } else { + //same logic as above without the hook invocation + fallbackExecutionChain = getFallbackObservable(); + } + } catch (Throwable ex) { + //If hook or user-fallback throws, then use that as the result of the fallback lookup + fallbackExecutionChain = Observable.error(ex); + } + + return fallbackExecutionChain + .doOnEach(setRequestContext) + .lift(new FallbackHookApplication(_cmd)) + .lift(new DeprecatedOnFallbackHookApplication(_cmd)) + .doOnNext(markFallbackEmit) + .doOnCompleted(markFallbackCompleted) + .onErrorResumeNext(handleFallbackError) + .doOnTerminate(singleSemaphoreRelease) + .doOnUnsubscribe(singleSemaphoreRelease); + } else { + return handleFallbackRejectionByEmittingError(); + } + } else { + return handleFallbackDisabledByEmittingError(originalException, failureType, message); + } + } + } + + private Observable getUserExecutionObservable(final AbstractCommand _cmd) { + Observable userObservable; + + try { + userObservable = getExecutionObservable(); + } catch (Throwable ex) { + // the run() method is a user provided implementation so can throw instead of using Observable.onError + // so we catch it here and turn it into Observable.error + userObservable = Observable.error(ex); + } + + return userObservable + .lift(new ExecutionHookApplication(_cmd)) + .lift(new DeprecatedOnRunHookApplication(_cmd)); + } + + private Observable handleRequestCacheHitAndEmitValues(final HystrixCommandResponseFromCache fromCache, final AbstractCommand _cmd) { + try { + executionHook.onCacheHit(this); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onCacheHit", hookEx); + } + + return fromCache.toObservableWithStateCopiedInto(this) + .doOnTerminate(new Action0() { + @Override + public void call() { + if (commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.TERMINAL)) { + cleanUpAfterResponseFromCache(false); //user code never ran + } else if (commandState.compareAndSet(CommandState.USER_CODE_EXECUTED, CommandState.TERMINAL)) { + cleanUpAfterResponseFromCache(true); //user code did run + } + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + if (commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.UNSUBSCRIBED)) { + cleanUpAfterResponseFromCache(false); //user code never ran + } else if (commandState.compareAndSet(CommandState.USER_CODE_EXECUTED, CommandState.UNSUBSCRIBED)) { + cleanUpAfterResponseFromCache(true); //user code did run + } + } + }); + } + + private void cleanUpAfterResponseFromCache(boolean commandExecutionStarted) { + Reference tl = timeoutTimer.get(); + if (tl != null) { + tl.clear(); + } + + final long latency = System.currentTimeMillis() - commandStartTimestamp; + executionResult = executionResult + .addEvent(-1, HystrixEventType.RESPONSE_FROM_CACHE) + .markUserThreadCompletion(latency) + .setNotExecutedInThread(); + ExecutionResult cacheOnlyForMetrics = ExecutionResult.from(HystrixEventType.RESPONSE_FROM_CACHE) + .markUserThreadCompletion(latency); + metrics.markCommandDone(cacheOnlyForMetrics, commandKey, threadPoolKey, commandExecutionStarted); + eventNotifier.markEvent(HystrixEventType.RESPONSE_FROM_CACHE, commandKey); + } + + private void handleCommandEnd(boolean commandExecutionStarted) { + Reference tl = timeoutTimer.get(); + if (tl != null) { + tl.clear(); + } + + long userThreadLatency = System.currentTimeMillis() - commandStartTimestamp; + executionResult = executionResult.markUserThreadCompletion((int) userThreadLatency); + if (executionResultAtTimeOfCancellation == null) { + metrics.markCommandDone(executionResult, commandKey, threadPoolKey, commandExecutionStarted); + } else { + metrics.markCommandDone(executionResultAtTimeOfCancellation, commandKey, threadPoolKey, commandExecutionStarted); + } + + if (endCurrentThreadExecutingCommand != null) { + endCurrentThreadExecutingCommand.call(); + } + } + + private Observable handleSemaphoreRejectionViaFallback() { + Exception semaphoreRejectionException = new RuntimeException("could not acquire a semaphore for execution"); + executionResult = executionResult.setExecutionException(semaphoreRejectionException); + eventNotifier.markEvent(HystrixEventType.SEMAPHORE_REJECTED, commandKey); + logger.debug("HystrixCommand Execution Rejection by Semaphore."); // debug only since we're throwing the exception and someone higher will do something with it + // retrieve a fallback or throw an exception if no fallback available + return getFallbackOrThrowException(this, HystrixEventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION, + "could not acquire a semaphore for execution", semaphoreRejectionException); + } + + private Observable handleShortCircuitViaFallback() { + // record that we are returning a short-circuited fallback + eventNotifier.markEvent(HystrixEventType.SHORT_CIRCUITED, commandKey); + // short-circuit and go directly to fallback (or throw an exception if no fallback implemented) + Exception shortCircuitException = new RuntimeException("Hystrix circuit short-circuited and is OPEN"); + executionResult = executionResult.setExecutionException(shortCircuitException); + try { + return getFallbackOrThrowException(this, HystrixEventType.SHORT_CIRCUITED, FailureType.SHORTCIRCUIT, + "short-circuited", shortCircuitException); + } catch (Exception e) { + return Observable.error(e); + } + } + + private Observable handleThreadPoolRejectionViaFallback(Exception underlying) { + eventNotifier.markEvent(HystrixEventType.THREAD_POOL_REJECTED, commandKey); + threadPool.markThreadRejection(); + // use a fallback instead (or throw exception if not implemented) + return getFallbackOrThrowException(this, HystrixEventType.THREAD_POOL_REJECTED, FailureType.REJECTED_THREAD_EXECUTION, "could not be queued for execution", underlying); + } + + private Observable handleTimeoutViaFallback() { + return getFallbackOrThrowException(this, HystrixEventType.TIMEOUT, FailureType.TIMEOUT, "timed-out", new TimeoutException()); + } + + private Observable handleBadRequestByEmittingError(Exception underlying) { + Exception toEmit = underlying; + + try { + long executionLatency = System.currentTimeMillis() - executionResult.getStartTimestamp(); + eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey); + executionResult = executionResult.addEvent((int) executionLatency, HystrixEventType.BAD_REQUEST); + Exception decorated = executionHook.onError(this, FailureType.BAD_REQUEST_EXCEPTION, underlying); + + if (decorated instanceof HystrixBadRequestException) { + toEmit = decorated; + } else { + logger.warn("ExecutionHook.onError returned an exception that was not an instance of HystrixBadRequestException so will be ignored.", decorated); + } + } catch (Exception hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onError", hookEx); + } + /* + * HystrixBadRequestException is treated differently and allowed to propagate without any stats tracking or fallback logic + */ + return Observable.error(toEmit); + } + + private Observable handleFailureViaFallback(Exception underlying) { + /** + * All other error handling + */ + logger.debug("Error executing HystrixCommand.run(). Proceeding to fallback logic ...", underlying); + + // report failure + eventNotifier.markEvent(HystrixEventType.FAILURE, commandKey); + + // record the exception + executionResult = executionResult.setException(underlying); + return getFallbackOrThrowException(this, HystrixEventType.FAILURE, FailureType.COMMAND_EXCEPTION, "failed", underlying); + } + + private Observable handleFallbackRejectionByEmittingError() { + long latencyWithFallback = System.currentTimeMillis() - executionResult.getStartTimestamp(); + eventNotifier.markEvent(HystrixEventType.FALLBACK_REJECTION, commandKey); + executionResult = executionResult.addEvent((int) latencyWithFallback, HystrixEventType.FALLBACK_REJECTION); + logger.debug("HystrixCommand Fallback Rejection."); // debug only since we're throwing the exception and someone higher will do something with it + // if we couldn't acquire a permit, we "fail fast" by throwing an exception + return Observable.error(new HystrixRuntimeException(FailureType.REJECTED_SEMAPHORE_FALLBACK, this.getClass(), getLogMessagePrefix() + " fallback execution rejected.", null, null)); + } + + private Observable handleFallbackDisabledByEmittingError(Exception underlying, FailureType failureType, String message) { + /* fallback is disabled so throw HystrixRuntimeException */ + logger.debug("Fallback disabled for HystrixCommand so will throw HystrixRuntimeException. ", underlying); // debug only since we're throwing the exception and someone higher will do something with it + eventNotifier.markEvent(HystrixEventType.FALLBACK_DISABLED, commandKey); + + /* executionHook for all errors */ + Exception wrapped = wrapWithOnErrorHook(failureType, underlying); + return Observable.error(new HystrixRuntimeException(failureType, this.getClass(), getLogMessagePrefix() + " " + message + " and fallback disabled.", wrapped, null)); + } + + protected boolean shouldNotBeWrapped(Throwable underlying) { + return underlying instanceof ExceptionNotWrappedByHystrix; + } + + /** + * Returns true iff the t was caused by a java.lang.Error that is unrecoverable. Note: not all java.lang.Errors are unrecoverable. + * @see for more context + * Solution taken from + * + * The specific set of Error that are considered unrecoverable are: + *

    + *
  • {@code StackOverflowError}
  • + *
  • {@code VirtualMachineError}
  • + *
  • {@code ThreadDeath}
  • + *
  • {@code LinkageError}
  • + *
+ * + * @param t throwable to check + * @return true iff the t was caused by a java.lang.Error that is unrecoverable + */ + private boolean isUnrecoverable(Throwable t) { + if (t != null && t.getCause() != null) { + Throwable cause = t.getCause(); + if (cause instanceof StackOverflowError) { + return true; + } else if (cause instanceof VirtualMachineError) { + return true; + } else if (cause instanceof ThreadDeath) { + return true; + } else if (cause instanceof LinkageError) { + return true; + } + } + return false; + } + + private boolean isRecoverableError(Throwable t) { + if (t != null && t.getCause() != null) { + Throwable cause = t.getCause(); + if (cause instanceof java.lang.Error) { + return !isUnrecoverable(t); + } + } + return false; + } + + protected void handleThreadEnd(AbstractCommand _cmd) { + HystrixCounters.decrementGlobalConcurrentThreads(); + threadPool.markThreadCompletion(); + try { + executionHook.onThreadComplete(_cmd); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onThreadComplete", hookEx); + } + } + + /** + * + * @return if onNext events should be reported on + * This affects {@link HystrixRequestLog}, and {@link HystrixEventNotifier} currently. + */ + protected boolean shouldOutputOnNextEvents() { + return false; + } + + private static class HystrixObservableTimeoutOperator implements Operator { + + final AbstractCommand originalCommand; + + public HystrixObservableTimeoutOperator(final AbstractCommand originalCommand) { + this.originalCommand = originalCommand; + } + + @Override + public Subscriber call(final Subscriber child) { + final CompositeSubscription s = new CompositeSubscription(); + // if the child unsubscribes we unsubscribe our parent as well + child.add(s); + + //capture the HystrixRequestContext upfront so that we can use it in the timeout thread later + final HystrixRequestContext hystrixRequestContext = HystrixRequestContext.getContextForCurrentThread(); + + TimerListener listener = new TimerListener() { + + @Override + public void tick() { + // if we can go from NOT_EXECUTED to TIMED_OUT then we do the timeout codepath + // otherwise it means we lost a race and the run() execution completed or did not start + if (originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)) { + // report timeout failure + originalCommand.eventNotifier.markEvent(HystrixEventType.TIMEOUT, originalCommand.commandKey); + + // shut down the original request + s.unsubscribe(); + + final HystrixContextRunnable timeoutRunnable = new HystrixContextRunnable(originalCommand.concurrencyStrategy, hystrixRequestContext, new Runnable() { + + @Override + public void run() { + child.onError(new HystrixTimeoutException()); + } + }); + + + timeoutRunnable.run(); + //if it did not start, then we need to mark a command start for concurrency metrics, and then issue the timeout + } + } + + @Override + public int getIntervalTimeInMilliseconds() { + return originalCommand.properties.executionTimeoutInMilliseconds().get(); + } + }; + + final Reference tl = HystrixTimer.getInstance().addTimerListener(listener); + + // set externally so execute/queue can see this + originalCommand.timeoutTimer.set(tl); + + /** + * If this subscriber receives values it means the parent succeeded/completed + */ + Subscriber parent = new Subscriber() { + + @Override + public void onCompleted() { + if (isNotTimedOut()) { + // stop timer and pass notification through + tl.clear(); + child.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + if (isNotTimedOut()) { + // stop timer and pass notification through + tl.clear(); + child.onError(e); + } + } + + @Override + public void onNext(R v) { + if (isNotTimedOut()) { + child.onNext(v); + } + } + + private boolean isNotTimedOut() { + // if already marked COMPLETED (by onNext) or succeeds in setting to COMPLETED + return originalCommand.isCommandTimedOut.get() == TimedOutStatus.COMPLETED || + originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.COMPLETED); + } + + }; + + // if s is unsubscribed we want to unsubscribe the parent + s.add(parent); + + return parent; + } + + } + + private static void setRequestContextIfNeeded(final HystrixRequestContext currentRequestContext) { + if (!HystrixRequestContext.isCurrentThreadInitialized()) { + // even if the user Observable doesn't have context we want it set for chained operators + HystrixRequestContext.setContextOnCurrentThread(currentRequestContext); + } + } + + /** + * Get the TryableSemaphore this HystrixCommand should use if a fallback occurs. + * + * @return TryableSemaphore + */ + protected TryableSemaphore getFallbackSemaphore() { + if (fallbackSemaphoreOverride == null) { + TryableSemaphore _s = fallbackSemaphorePerCircuit.get(commandKey.name()); + if (_s == null) { + // we didn't find one cache so setup + fallbackSemaphorePerCircuit.putIfAbsent(commandKey.name(), new TryableSemaphoreActual(properties.fallbackIsolationSemaphoreMaxConcurrentRequests())); + // assign whatever got set (this or another thread) + return fallbackSemaphorePerCircuit.get(commandKey.name()); + } else { + return _s; + } + } else { + return fallbackSemaphoreOverride; + } + } + + /** + * Get the TryableSemaphore this HystrixCommand should use for execution if not running in a separate thread. + * + * @return TryableSemaphore + */ + protected TryableSemaphore getExecutionSemaphore() { + if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.SEMAPHORE) { + if (executionSemaphoreOverride == null) { + TryableSemaphore _s = executionSemaphorePerCircuit.get(commandKey.name()); + if (_s == null) { + // we didn't find one cache so setup + executionSemaphorePerCircuit.putIfAbsent(commandKey.name(), new TryableSemaphoreActual(properties.executionIsolationSemaphoreMaxConcurrentRequests())); + // assign whatever got set (this or another thread) + return executionSemaphorePerCircuit.get(commandKey.name()); + } else { + return _s; + } + } else { + return executionSemaphoreOverride; + } + } else { + // return NoOp implementation since we're not using SEMAPHORE isolation + return TryableSemaphoreNoOp.DEFAULT; + } + } + + /** + * Each concrete implementation of AbstractCommand should return the name of the fallback method as a String + * This will be used to determine if the fallback "exists" for firing the onFallbackStart/onFallbackError hooks + * @deprecated This functionality is replaced by {@link #isFallbackUserDefined}, which is less implementation-aware + * @return method name of fallback + */ + @Deprecated + protected abstract String getFallbackMethodName(); + + protected abstract boolean isFallbackUserDefined(); + + /** + * @return {@link HystrixCommandGroupKey} used to group together multiple {@link AbstractCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace with, + * common business purpose etc. + */ + public HystrixCommandGroupKey getCommandGroup() { + return commandGroup; + } + + /** + * @return {@link HystrixCommandKey} identifying this command instance for statistics, circuit-breaker, properties, etc. + */ + public HystrixCommandKey getCommandKey() { + return commandKey; + } + + /** + * @return {@link HystrixThreadPoolKey} identifying which thread-pool this command uses (when configured to run on separate threads via + * {@link HystrixCommandProperties#executionIsolationStrategy()}). + */ + public HystrixThreadPoolKey getThreadPoolKey() { + return threadPoolKey; + } + + /* package */HystrixCircuitBreaker getCircuitBreaker() { + return circuitBreaker; + } + + /** + * The {@link HystrixCommandMetrics} associated with this {@link AbstractCommand} instance. + * + * @return HystrixCommandMetrics + */ + public HystrixCommandMetrics getMetrics() { + return metrics; + } + + /** + * The {@link HystrixCommandProperties} associated with this {@link AbstractCommand} instance. + * + * @return HystrixCommandProperties + */ + public HystrixCommandProperties getProperties() { + return properties; + } + + /* ******************************************************************************** */ + /* ******************************************************************************** */ + /* Operators that implement hook application */ + /* ******************************************************************************** */ + /* ******************************************************************************** */ + + private class ExecutionHookApplication implements Operator { + private final HystrixInvokable cmd; + + ExecutionHookApplication(HystrixInvokable cmd) { + this.cmd = cmd; + } + + @Override + public Subscriber call(final Subscriber subscriber) { + return new Subscriber(subscriber) { + @Override + public void onCompleted() { + try { + executionHook.onExecutionSuccess(cmd); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onExecutionSuccess", hookEx); + } + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + Exception wrappedEx = wrapWithOnExecutionErrorHook(e); + subscriber.onError(wrappedEx); + } + + @Override + public void onNext(R r) { + R wrappedValue = wrapWithOnExecutionEmitHook(r); + subscriber.onNext(wrappedValue); + } + }; + } + } + + private class FallbackHookApplication implements Operator { + private final HystrixInvokable cmd; + + FallbackHookApplication(HystrixInvokable cmd) { + this.cmd = cmd; + } + + @Override + public Subscriber call(final Subscriber subscriber) { + return new Subscriber(subscriber) { + @Override + public void onCompleted() { + try { + executionHook.onFallbackSuccess(cmd); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onFallbackSuccess", hookEx); + } + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + Exception wrappedEx = wrapWithOnFallbackErrorHook(e); + subscriber.onError(wrappedEx); + } + + @Override + public void onNext(R r) { + R wrappedValue = wrapWithOnFallbackEmitHook(r); + subscriber.onNext(wrappedValue); + } + }; + } + } + + @Deprecated //separated out to make it cleanly removable + private class DeprecatedOnRunHookApplication implements Operator { + + private final HystrixInvokable cmd; + + DeprecatedOnRunHookApplication(HystrixInvokable cmd) { + this.cmd = cmd; + } + + @Override + public Subscriber call(final Subscriber subscriber) { + return new Subscriber(subscriber) { + @Override + public void onCompleted() { + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable t) { + Exception e = getExceptionFromThrowable(t); + try { + Exception wrappedEx = executionHook.onRunError(cmd, e); + subscriber.onError(wrappedEx); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onRunError", hookEx); + subscriber.onError(e); + } + } + + @Override + public void onNext(R r) { + try { + R wrappedValue = executionHook.onRunSuccess(cmd, r); + subscriber.onNext(wrappedValue); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onRunSuccess", hookEx); + subscriber.onNext(r); + } + } + }; + } + } + + @Deprecated //separated out to make it cleanly removable + private class DeprecatedOnFallbackHookApplication implements Operator { + + private final HystrixInvokable cmd; + + DeprecatedOnFallbackHookApplication(HystrixInvokable cmd) { + this.cmd = cmd; + } + + @Override + public Subscriber call(final Subscriber subscriber) { + return new Subscriber(subscriber) { + @Override + public void onCompleted() { + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable t) { + //no need to call a hook here. FallbackHookApplication is already calling the proper and non-deprecated hook + subscriber.onError(t); + } + + @Override + public void onNext(R r) { + try { + R wrappedValue = executionHook.onFallbackSuccess(cmd, r); + subscriber.onNext(wrappedValue); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onFallbackSuccess", hookEx); + subscriber.onNext(r); + } + } + }; + } + } + + private Exception wrapWithOnExecutionErrorHook(Throwable t) { + Exception e = getExceptionFromThrowable(t); + try { + return executionHook.onExecutionError(this, e); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onExecutionError", hookEx); + return e; + } + } + + private Exception wrapWithOnFallbackErrorHook(Throwable t) { + Exception e = getExceptionFromThrowable(t); + try { + if (isFallbackUserDefined()) { + return executionHook.onFallbackError(this, e); + } else { + return e; + } + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onFallbackError", hookEx); + return e; + } + } + + private Exception wrapWithOnErrorHook(FailureType failureType, Throwable t) { + Exception e = getExceptionFromThrowable(t); + try { + return executionHook.onError(this, failureType, e); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onError", hookEx); + return e; + } + } + + private R wrapWithOnExecutionEmitHook(R r) { + try { + return executionHook.onExecutionEmit(this, r); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onExecutionEmit", hookEx); + return r; + } + } + + private R wrapWithOnFallbackEmitHook(R r) { + try { + return executionHook.onFallbackEmit(this, r); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onFallbackEmit", hookEx); + return r; + } + } + + private R wrapWithOnEmitHook(R r) { + try { + return executionHook.onEmit(this, r); + } catch (Throwable hookEx) { + logger.warn("Error calling HystrixCommandExecutionHook.onEmit", hookEx); + return r; + } + } + + /** + * Take an Exception and determine whether to throw it, its cause or a new HystrixRuntimeException. + *

+ * This will only throw an HystrixRuntimeException, HystrixBadRequestException, IllegalStateException + * or any exception that implements ExceptionNotWrappedByHystrix. + * + * @param e initial exception + * @return HystrixRuntimeException, HystrixBadRequestException or IllegalStateException + */ + protected Throwable decomposeException(Exception e) { + if (e instanceof IllegalStateException) { + return (IllegalStateException) e; + } + if (e instanceof HystrixBadRequestException) { + if (shouldNotBeWrapped(e.getCause())) { + return e.getCause(); + } + return (HystrixBadRequestException) e; + } + if (e.getCause() instanceof HystrixBadRequestException) { + if(shouldNotBeWrapped(e.getCause().getCause())) { + return e.getCause().getCause(); + } + return (HystrixBadRequestException) e.getCause(); + } + if (e instanceof HystrixRuntimeException) { + return (HystrixRuntimeException) e; + } + // if we have an exception we know about we'll throw it directly without the wrapper exception + if (e.getCause() instanceof HystrixRuntimeException) { + return (HystrixRuntimeException) e.getCause(); + } + if (shouldNotBeWrapped(e)) { + return e; + } + if (shouldNotBeWrapped(e.getCause())) { + return e.getCause(); + } + // we don't know what kind of exception this is so create a generic message and throw a new HystrixRuntimeException + String message = getLogMessagePrefix() + " failed while executing."; + logger.debug(message, e); // debug only since we're throwing the exception and someone higher will do something with it + return new HystrixRuntimeException(FailureType.COMMAND_EXCEPTION, this.getClass(), message, e, null); + + } + + /* ******************************************************************************** */ + /* ******************************************************************************** */ + /* TryableSemaphore */ + /* ******************************************************************************** */ + /* ******************************************************************************** */ + + /** + * Semaphore that only supports tryAcquire and never blocks and that supports a dynamic permit count. + *

+ * Using AtomicInteger increment/decrement instead of java.util.concurrent.Semaphore since we don't need blocking and need a custom implementation to get the dynamic permit count and since + * AtomicInteger achieves the same behavior and performance without the more complex implementation of the actual Semaphore class using AbstractQueueSynchronizer. + */ + /* package */static class TryableSemaphoreActual implements TryableSemaphore { + protected final HystrixProperty numberOfPermits; + private final AtomicInteger count = new AtomicInteger(0); + + public TryableSemaphoreActual(HystrixProperty numberOfPermits) { + this.numberOfPermits = numberOfPermits; + } + + @Override + public boolean tryAcquire() { + int currentCount = count.incrementAndGet(); + if (currentCount > numberOfPermits.get()) { + count.decrementAndGet(); + return false; + } else { + return true; + } + } + + @Override + public void release() { + count.decrementAndGet(); + } + + @Override + public int getNumberOfPermitsUsed() { + return count.get(); + } + + } + + /* package */static class TryableSemaphoreNoOp implements TryableSemaphore { + + public static final TryableSemaphore DEFAULT = new TryableSemaphoreNoOp(); + + @Override + public boolean tryAcquire() { + return true; + } + + @Override + public void release() { + + } + + @Override + public int getNumberOfPermitsUsed() { + return 0; + } + + } + + /* package */static interface TryableSemaphore { + + /** + * Use like this: + *

+ * + *

+         * if (s.tryAcquire()) {
+         * try {
+         * // do work that is protected by 's'
+         * } finally {
+         * s.release();
+         * }
+         * }
+         * 
+ * + * @return boolean + */ + public abstract boolean tryAcquire(); + + /** + * ONLY call release if tryAcquire returned true. + *

+ * + *

+         * if (s.tryAcquire()) {
+         * try {
+         * // do work that is protected by 's'
+         * } finally {
+         * s.release();
+         * }
+         * }
+         * 
+ */ + public abstract void release(); + + public abstract int getNumberOfPermitsUsed(); + + } + + /* ******************************************************************************** */ + /* ******************************************************************************** */ + /* RequestCache */ + /* ******************************************************************************** */ + /* ******************************************************************************** */ + + /** + * Key to be used for request caching. + *

+ * By default this returns null which means "do not cache". + *

+ * To enable caching override this method and return a string key uniquely representing the state of a command instance. + *

+ * If multiple command instances in the same request scope match keys then only the first will be executed and all others returned from cache. + * + * @return cacheKey + */ + protected String getCacheKey() { + return null; + } + + public String getPublicCacheKey() { + return getCacheKey(); + } + + protected boolean isRequestCachingEnabled() { + return properties.requestCacheEnabled().get() && getCacheKey() != null; + } + + protected String getLogMessagePrefix() { + return getCommandKey().name(); + } + + /** + * Whether the 'circuit-breaker' is open meaning that execute() will immediately return + * the getFallback() response and not attempt a HystrixCommand execution. + * + * 4 columns are ForcedOpen | ForcedClosed | CircuitBreaker open due to health ||| Expected Result + * + * T | T | T ||| OPEN (true) + * T | T | F ||| OPEN (true) + * T | F | T ||| OPEN (true) + * T | F | F ||| OPEN (true) + * F | T | T ||| CLOSED (false) + * F | T | F ||| CLOSED (false) + * F | F | T ||| OPEN (true) + * F | F | F ||| CLOSED (false) + * + * @return boolean + */ + public boolean isCircuitBreakerOpen() { + return properties.circuitBreakerForceOpen().get() || (!properties.circuitBreakerForceClosed().get() && circuitBreaker.isOpen()); + } + + /** + * If this command has completed execution either successfully, via fallback or failure. + * + * @return boolean + */ + public boolean isExecutionComplete() { + return commandState.get() == CommandState.TERMINAL; + } + + /** + * Whether the execution occurred in a separate thread. + *

+ * This should be called only once execute()/queue()/fireOrForget() are called otherwise it will always return false. + *

+ * This specifies if a thread execution actually occurred, not just if it is configured to be executed in a thread. + * + * @return boolean + */ + public boolean isExecutedInThread() { + return getCommandResult().isExecutedInThread(); + } + + /** + * Whether the response was returned successfully either by executing run() or from cache. + * + * @return boolean + */ + public boolean isSuccessfulExecution() { + return getCommandResult().getEventCounts().contains(HystrixEventType.SUCCESS); + } + + /** + * Whether the run() resulted in a failure (exception). + * + * @return boolean + */ + public boolean isFailedExecution() { + return getCommandResult().getEventCounts().contains(HystrixEventType.FAILURE); + } + + /** + * Get the Throwable/Exception thrown that caused the failure. + *

+ * If isFailedExecution() == true then this would represent the Exception thrown by the run() method. + *

+ * If isFailedExecution() == false then this would return null. + * + * @return Throwable or null + */ + public Throwable getFailedExecutionException() { + return executionResult.getException(); + } + + /** + * Get the Throwable/Exception emitted by this command instance prior to checking the fallback. + * This exception instance may have been generated via a number of mechanisms: + * 1) failed execution (in this case, same result as {@link #getFailedExecutionException()}. + * 2) timeout + * 3) short-circuit + * 4) rejection + * 5) bad request + * + * If the command execution was successful, then this exception instance is null (there was no exception) + * + * Note that the caller of the command may not receive this exception, as fallbacks may be served as a response to + * the exception. + * + * @return Throwable or null + */ + public Throwable getExecutionException() { + return executionResult.getExecutionException(); + } + + /** + * Whether the response received from was the result of some type of failure + * and getFallback() being called. + * + * @return boolean + */ + public boolean isResponseFromFallback() { + return getCommandResult().getEventCounts().contains(HystrixEventType.FALLBACK_SUCCESS); + } + + /** + * Whether the response received was the result of a timeout + * and getFallback() being called. + * + * @return boolean + */ + public boolean isResponseTimedOut() { + return getCommandResult().getEventCounts().contains(HystrixEventType.TIMEOUT); + } + + /** + * Whether the response received was a fallback as result of being + * short-circuited (meaning isCircuitBreakerOpen() == true) and getFallback() being called. + * + * @return boolean + */ + public boolean isResponseShortCircuited() { + return getCommandResult().getEventCounts().contains(HystrixEventType.SHORT_CIRCUITED); + } + + /** + * Whether the response is from cache and run() was not invoked. + * + * @return boolean + */ + public boolean isResponseFromCache() { + return isResponseFromCache; + } + + /** + * Whether the response received was a fallback as result of being rejected via sempahore + * + * @return boolean + */ + public boolean isResponseSemaphoreRejected() { + return getCommandResult().isResponseSemaphoreRejected(); + } + + /** + * Whether the response received was a fallback as result of being rejected via threadpool + * + * @return boolean + */ + public boolean isResponseThreadPoolRejected() { + return getCommandResult().isResponseThreadPoolRejected(); + } + + /** + * Whether the response received was a fallback as result of being rejected (either via threadpool or semaphore) + * + * @return boolean + */ + public boolean isResponseRejected() { + return getCommandResult().isResponseRejected(); + } + + /** + * List of HystrixCommandEventType enums representing events that occurred during execution. + *

+ * Examples of events are SUCCESS, FAILURE, TIMEOUT, and SHORT_CIRCUITED + * + * @return {@code List} + */ + public List getExecutionEvents() { + return getCommandResult().getOrderedList(); + } + + private ExecutionResult getCommandResult() { + ExecutionResult resultToReturn; + if (executionResultAtTimeOfCancellation == null) { + resultToReturn = executionResult; + } else { + resultToReturn = executionResultAtTimeOfCancellation; + } + + if (isResponseFromCache) { + resultToReturn = resultToReturn.addEvent(HystrixEventType.RESPONSE_FROM_CACHE); + } + + return resultToReturn; + } + + /** + * Number of emissions of the execution of a command. Only interesting in the streaming case. + * @return number of OnNext emissions by a streaming command + */ + @Override + public int getNumberEmissions() { + return getCommandResult().getEventCounts().getCount(HystrixEventType.EMIT); + } + + /** + * Number of emissions of the execution of a fallback. Only interesting in the streaming case. + * @return number of OnNext emissions by a streaming fallback + */ + @Override + public int getNumberFallbackEmissions() { + return getCommandResult().getEventCounts().getCount(HystrixEventType.FALLBACK_EMIT); + } + + @Override + public int getNumberCollapsed() { + return getCommandResult().getEventCounts().getCount(HystrixEventType.COLLAPSED); + } + + @Override + public HystrixCollapserKey getOriginatingCollapserKey() { + return executionResult.getCollapserKey(); + } + + /** + * The execution time of this command instance in milliseconds, or -1 if not executed. + * + * @return int + */ + public int getExecutionTimeInMilliseconds() { + return getCommandResult().getExecutionLatency(); + } + + /** + * Time in Nanos when this command instance's run method was called, or -1 if not executed + * for e.g., command threw an exception + * + * @return long + */ + public long getCommandRunStartTimeInNanos() { + return executionResult.getCommandRunStartTimeInNanos(); + } + + @Override + public ExecutionResult.EventCounts getEventCounts() { + return getCommandResult().getEventCounts(); + } + + protected Exception getExceptionFromThrowable(Throwable t) { + Exception e; + if (t instanceof Exception) { + e = (Exception) t; + } else { + // Hystrix 1.x uses Exception, not Throwable so to prevent a breaking change Throwable will be wrapped in Exception + e = new Exception("Throwable caught while executing.", t); + } + return e; + } + + private static class ExecutionHookDeprecationWrapper extends HystrixCommandExecutionHook { + + private final HystrixCommandExecutionHook actual; + + ExecutionHookDeprecationWrapper(HystrixCommandExecutionHook actual) { + this.actual = actual; + } + + @Override + public T onEmit(HystrixInvokable commandInstance, T value) { + return actual.onEmit(commandInstance, value); + } + + @Override + public void onSuccess(HystrixInvokable commandInstance) { + actual.onSuccess(commandInstance); + } + + @Override + public void onExecutionStart(HystrixInvokable commandInstance) { + actual.onExecutionStart(commandInstance); + } + + @Override + public T onExecutionEmit(HystrixInvokable commandInstance, T value) { + return actual.onExecutionEmit(commandInstance, value); + } + + @Override + public Exception onExecutionError(HystrixInvokable commandInstance, Exception e) { + return actual.onExecutionError(commandInstance, e); + } + + @Override + public void onExecutionSuccess(HystrixInvokable commandInstance) { + actual.onExecutionSuccess(commandInstance); + } + + @Override + public T onFallbackEmit(HystrixInvokable commandInstance, T value) { + return actual.onFallbackEmit(commandInstance, value); + } + + @Override + public void onFallbackSuccess(HystrixInvokable commandInstance) { + actual.onFallbackSuccess(commandInstance); + } + + @Override + @Deprecated + public void onRunStart(HystrixCommand commandInstance) { + actual.onRunStart(commandInstance); + } + + @Override + public void onRunStart(HystrixInvokable commandInstance) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + onRunStart(c); + } + actual.onRunStart(commandInstance); + } + + @Override + @Deprecated + public T onRunSuccess(HystrixCommand commandInstance, T response) { + return actual.onRunSuccess(commandInstance, response); + } + + @Override + @Deprecated + public T onRunSuccess(HystrixInvokable commandInstance, T response) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + response = onRunSuccess(c, response); + } + return actual.onRunSuccess(commandInstance, response); + } + + @Override + @Deprecated + public Exception onRunError(HystrixCommand commandInstance, Exception e) { + return actual.onRunError(commandInstance, e); + } + + @Override + @Deprecated + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + e = onRunError(c, e); + } + return actual.onRunError(commandInstance, e); + } + + @Override + @Deprecated + public void onFallbackStart(HystrixCommand commandInstance) { + actual.onFallbackStart(commandInstance); + } + + @Override + public void onFallbackStart(HystrixInvokable commandInstance) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + onFallbackStart(c); + } + actual.onFallbackStart(commandInstance); + } + + @Override + @Deprecated + public T onFallbackSuccess(HystrixCommand commandInstance, T fallbackResponse) { + return actual.onFallbackSuccess(commandInstance, fallbackResponse); + } + + @Override + @Deprecated + public T onFallbackSuccess(HystrixInvokable commandInstance, T fallbackResponse) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + fallbackResponse = onFallbackSuccess(c, fallbackResponse); + } + return actual.onFallbackSuccess(commandInstance, fallbackResponse); + } + + @Override + @Deprecated + public Exception onFallbackError(HystrixCommand commandInstance, Exception e) { + return actual.onFallbackError(commandInstance, e); + } + + @Override + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + e = onFallbackError(c, e); + } + return actual.onFallbackError(commandInstance, e); + } + + @Override + @Deprecated + public void onStart(HystrixCommand commandInstance) { + actual.onStart(commandInstance); + } + + @Override + public void onStart(HystrixInvokable commandInstance) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + onStart(c); + } + actual.onStart(commandInstance); + } + + @Override + @Deprecated + public T onComplete(HystrixCommand commandInstance, T response) { + return actual.onComplete(commandInstance, response); + } + + @Override + @Deprecated + public T onComplete(HystrixInvokable commandInstance, T response) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + response = onComplete(c, response); + } + return actual.onComplete(commandInstance, response); + } + + @Override + @Deprecated + public Exception onError(HystrixCommand commandInstance, FailureType failureType, Exception e) { + return actual.onError(commandInstance, failureType, e); + } + + @Override + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + e = onError(c, failureType, e); + } + return actual.onError(commandInstance, failureType, e); + } + + @Override + @Deprecated + public void onThreadStart(HystrixCommand commandInstance) { + actual.onThreadStart(commandInstance); + } + + @Override + public void onThreadStart(HystrixInvokable commandInstance) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + onThreadStart(c); + } + actual.onThreadStart(commandInstance); + } + + @Override + @Deprecated + public void onThreadComplete(HystrixCommand commandInstance) { + actual.onThreadComplete(commandInstance); + } + + @Override + public void onThreadComplete(HystrixInvokable commandInstance) { + HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); + if (c != null) { + onThreadComplete(c); + } + actual.onThreadComplete(commandInstance); + } + + @Override + public void onCacheHit(HystrixInvokable commandInstance) { + actual.onCacheHit(commandInstance); + } + + @Override + public void onUnsubscribe(HystrixInvokable commandInstance) { + actual.onUnsubscribe(commandInstance); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private HystrixCommand getHystrixCommandFromAbstractIfApplicable(HystrixInvokable commandInstance) { + if (commandInstance instanceof HystrixCommand) { + return (HystrixCommand) commandInstance; + } else { + return null; + } + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/ExecutionResult.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/ExecutionResult.java new file mode 100644 index 0000000..8ae1d1a --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/ExecutionResult.java @@ -0,0 +1,379 @@ +/** + * Copyright 2016 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 java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +/** + * Immutable holder class for the status of command execution. + *

+ * This object can be referenced and "modified" by parent and child threads as well as by different instances of HystrixCommand since + * 1 instance could create an ExecutionResult, cache a Future that refers to it, a 2nd instance execution then retrieves a Future + * from cache and wants to append RESPONSE_FROM_CACHE to whatever the ExecutionResult was from the first command execution. + *

+ * This being immutable forces and ensure thread-safety instead of using AtomicInteger/ConcurrentLinkedQueue and determining + * when it's safe to mutate the object directly versus needing to deep-copy clone to a new instance. + */ +public class ExecutionResult { + private final EventCounts eventCounts; + private final Exception failedExecutionException; + private final Exception executionException; + private final long startTimestamp; + private final int executionLatency; //time spent in run() method + private final int userThreadLatency; //time elapsed between caller thread submitting request and response being visible to it + private final boolean executionOccurred; + private final boolean isExecutedInThread; + private final HystrixCollapserKey collapserKey; + + private static final HystrixEventType[] ALL_EVENT_TYPES = HystrixEventType.values(); + private static final int NUM_EVENT_TYPES = ALL_EVENT_TYPES.length; + private static final BitSet EXCEPTION_PRODUCING_EVENTS = new BitSet(NUM_EVENT_TYPES); + private static final BitSet TERMINAL_EVENTS = new BitSet(NUM_EVENT_TYPES); + + static { + for (HystrixEventType eventType: HystrixEventType.EXCEPTION_PRODUCING_EVENT_TYPES) { + EXCEPTION_PRODUCING_EVENTS.set(eventType.ordinal()); + } + + for (HystrixEventType eventType: HystrixEventType.TERMINAL_EVENT_TYPES) { + TERMINAL_EVENTS.set(eventType.ordinal()); + } + } + + public static class EventCounts { + private final BitSet events; + private final int numEmissions; + private final int numFallbackEmissions; + private final int numCollapsed; + + EventCounts() { + this.events = new BitSet(NUM_EVENT_TYPES); + this.numEmissions = 0; + this.numFallbackEmissions = 0; + this.numCollapsed = 0; + } + + EventCounts(BitSet events, int numEmissions, int numFallbackEmissions, int numCollapsed) { + this.events = events; + this.numEmissions = numEmissions; + this.numFallbackEmissions = numFallbackEmissions; + this.numCollapsed = numCollapsed; + } + + EventCounts(HystrixEventType... eventTypes) { + BitSet newBitSet = new BitSet(NUM_EVENT_TYPES); + int localNumEmits = 0; + int localNumFallbackEmits = 0; + int localNumCollapsed = 0; + for (HystrixEventType eventType: eventTypes) { + switch (eventType) { + case EMIT: + newBitSet.set(HystrixEventType.EMIT.ordinal()); + localNumEmits++; + break; + case FALLBACK_EMIT: + newBitSet.set(HystrixEventType.FALLBACK_EMIT.ordinal()); + localNumFallbackEmits++; + break; + case COLLAPSED: + newBitSet.set(HystrixEventType.COLLAPSED.ordinal()); + localNumCollapsed++; + break; + default: + newBitSet.set(eventType.ordinal()); + break; + } + } + this.events = newBitSet; + this.numEmissions = localNumEmits; + this.numFallbackEmissions = localNumFallbackEmits; + this.numCollapsed = localNumCollapsed; + } + + EventCounts plus(HystrixEventType eventType) { + return plus(eventType, 1); + } + + EventCounts plus(HystrixEventType eventType, int count) { + BitSet newBitSet = (BitSet) events.clone(); + int localNumEmits = numEmissions; + int localNumFallbackEmits = numFallbackEmissions; + int localNumCollapsed = numCollapsed; + switch (eventType) { + case EMIT: + newBitSet.set(HystrixEventType.EMIT.ordinal()); + localNumEmits += count; + break; + case FALLBACK_EMIT: + newBitSet.set(HystrixEventType.FALLBACK_EMIT.ordinal()); + localNumFallbackEmits += count; + break; + case COLLAPSED: + newBitSet.set(HystrixEventType.COLLAPSED.ordinal()); + localNumCollapsed += count; + break; + default: + newBitSet.set(eventType.ordinal()); + break; + } + return new EventCounts(newBitSet, localNumEmits, localNumFallbackEmits, localNumCollapsed); + } + + public boolean contains(HystrixEventType eventType) { + return events.get(eventType.ordinal()); + } + + public boolean containsAnyOf(BitSet other) { + return events.intersects(other); + } + + public int getCount(HystrixEventType eventType) { + switch (eventType) { + case EMIT: return numEmissions; + case FALLBACK_EMIT: return numFallbackEmissions; + case EXCEPTION_THROWN: return containsAnyOf(EXCEPTION_PRODUCING_EVENTS) ? 1 : 0; + case COLLAPSED: return numCollapsed; + default: return contains(eventType) ? 1 : 0; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EventCounts that = (EventCounts) o; + + if (numEmissions != that.numEmissions) return false; + if (numFallbackEmissions != that.numFallbackEmissions) return false; + if (numCollapsed != that.numCollapsed) return false; + return events.equals(that.events); + + } + + @Override + public int hashCode() { + int result = events.hashCode(); + result = 31 * result + numEmissions; + result = 31 * result + numFallbackEmissions; + result = 31 * result + numCollapsed; + return result; + } + + @Override + public String toString() { + return "EventCounts{" + + "events=" + events + + ", numEmissions=" + numEmissions + + ", numFallbackEmissions=" + numFallbackEmissions + + ", numCollapsed=" + numCollapsed + + '}'; + } + } + + private ExecutionResult(EventCounts eventCounts, long startTimestamp, int executionLatency, + int userThreadLatency, Exception failedExecutionException, Exception executionException, + boolean executionOccurred, boolean isExecutedInThread, HystrixCollapserKey collapserKey) { + this.eventCounts = eventCounts; + this.startTimestamp = startTimestamp; + this.executionLatency = executionLatency; + this.userThreadLatency = userThreadLatency; + this.failedExecutionException = failedExecutionException; + this.executionException = executionException; + this.executionOccurred = executionOccurred; + this.isExecutedInThread = isExecutedInThread; + this.collapserKey = collapserKey; + } + + // we can return a static version since it's immutable + static ExecutionResult EMPTY = ExecutionResult.from(); + + public static ExecutionResult from(HystrixEventType... eventTypes) { + boolean didExecutionOccur = false; + for (HystrixEventType eventType: eventTypes) { + if (didExecutionOccur(eventType)) { + didExecutionOccur = true; + } + } + return new ExecutionResult(new EventCounts(eventTypes), -1L, -1, -1, null, null, didExecutionOccur, false, null); + } + + private static boolean didExecutionOccur(HystrixEventType eventType) { + switch (eventType) { + case SUCCESS: return true; + case FAILURE: return true; + case BAD_REQUEST: return true; + case TIMEOUT: return true; + case CANCELLED: return true; + default: return false; + } + } + + public ExecutionResult setExecutionOccurred() { + return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, + failedExecutionException, executionException, true, isExecutedInThread, collapserKey); + } + + public ExecutionResult setExecutionLatency(int executionLatency) { + return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, + failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); + } + + public ExecutionResult setException(Exception e) { + return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, e, + executionException, executionOccurred, isExecutedInThread, collapserKey); + } + + public ExecutionResult setExecutionException(Exception executionException) { + return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, + failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); + } + + public ExecutionResult setInvocationStartTime(long startTimestamp) { + return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, + failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); + } + + public ExecutionResult setExecutedInThread() { + return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, + failedExecutionException, executionException, executionOccurred, true, collapserKey); + } + + public ExecutionResult setNotExecutedInThread() { + return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, + failedExecutionException, executionException, executionOccurred, false, collapserKey); + } + + public ExecutionResult markCollapsed(HystrixCollapserKey collapserKey, int sizeOfBatch) { + return new ExecutionResult(eventCounts.plus(HystrixEventType.COLLAPSED, sizeOfBatch), startTimestamp, executionLatency, userThreadLatency, + failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); + } + + public ExecutionResult markUserThreadCompletion(long userThreadLatency) { + if (startTimestamp > 0 && !isResponseRejected()) { + /* execution time (must occur before terminal state otherwise a race condition can occur if requested by client) */ + return new ExecutionResult(eventCounts, startTimestamp, executionLatency, (int) userThreadLatency, + failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); + } else { + return this; + } + } + + /** + * Creates a new ExecutionResult by adding the defined 'event' to the ones on the current instance. + * + * @param eventType event to add + * @return new {@link ExecutionResult} with event added + */ + public ExecutionResult addEvent(HystrixEventType eventType) { + return new ExecutionResult(eventCounts.plus(eventType), startTimestamp, executionLatency, + userThreadLatency, failedExecutionException, executionException, + executionOccurred, isExecutedInThread, collapserKey); + } + + public ExecutionResult addEvent(int executionLatency, HystrixEventType eventType) { + if (startTimestamp >= 0 && !isResponseRejected()) { + return new ExecutionResult(eventCounts.plus(eventType), startTimestamp, executionLatency, + userThreadLatency, failedExecutionException, executionException, + executionOccurred, isExecutedInThread, collapserKey); + } else { + return addEvent(eventType); + } + } + + public EventCounts getEventCounts() { + return eventCounts; + } + + public long getStartTimestamp() { + return startTimestamp; + } + + public int getExecutionLatency() { + return executionLatency; + } + + public int getUserThreadLatency() { + return userThreadLatency; + } + + public long getCommandRunStartTimeInNanos() { + return startTimestamp * 1000 * 1000; + } + + public Exception getException() { + return failedExecutionException; + } + + public Exception getExecutionException() { + return executionException; + } + + public HystrixCollapserKey getCollapserKey() { + return collapserKey; + } + + public boolean isResponseSemaphoreRejected() { + return eventCounts.contains(HystrixEventType.SEMAPHORE_REJECTED); + } + + public boolean isResponseThreadPoolRejected() { + return eventCounts.contains(HystrixEventType.THREAD_POOL_REJECTED); + } + + public boolean isResponseRejected() { + return isResponseThreadPoolRejected() || isResponseSemaphoreRejected(); + } + + public List getOrderedList() { + List eventList = new ArrayList(); + for (HystrixEventType eventType: ALL_EVENT_TYPES) { + if (eventCounts.contains(eventType)) { + eventList.add(eventType); + } + } + return eventList; + } + + public boolean isExecutedInThread() { + return isExecutedInThread; + } + + public boolean executionOccurred() { + return executionOccurred; + } + + public boolean containsTerminalEvent() { + return eventCounts.containsAnyOf(TERMINAL_EVENTS); + } + + @Override + public String toString() { + return "ExecutionResult{" + + "eventCounts=" + eventCounts + + ", failedExecutionException=" + failedExecutionException + + ", executionException=" + executionException + + ", startTimestamp=" + startTimestamp + + ", executionLatency=" + executionLatency + + ", userThreadLatency=" + userThreadLatency + + ", executionOccurred=" + executionOccurred + + ", isExecutedInThread=" + isExecutedInThread + + ", collapserKey=" + collapserKey + + '}'; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/Hystrix.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/Hystrix.java new file mode 100644 index 0000000..599297e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/Hystrix.java @@ -0,0 +1,209 @@ +/** + * 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 java.util.NoSuchElementException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import rx.functions.Action0; + +/** + * Lifecycle management of Hystrix. + */ +public class Hystrix { + + private static final Logger logger = LoggerFactory.getLogger(Hystrix.class); + + /** + * Reset state and release resources in use (such as thread-pools). + *

+ * NOTE: This can result in race conditions if HystrixCommands are concurrently being executed. + *

+ */ + public static void reset() { + // shutdown thread-pools + HystrixThreadPool.Factory.shutdown(); + _reset(); + } + + /** + * Reset state and release resources in use (such as threadpools) and wait for completion. + *

+ * NOTE: This can result in race conditions if HystrixCommands are concurrently being executed. + *

+ * + * @param time + * time to wait for thread-pools to shutdown + * @param unit + * {@link TimeUnit} for
time
to wait for thread-pools to shutdown + */ + public static void reset(long time, TimeUnit unit) { + // shutdown thread-pools + HystrixThreadPool.Factory.shutdown(time, unit); + _reset(); + } + + /** + * Reset logic that doesn't have time/TimeUnit arguments. + */ + private static void _reset() { + // clear metrics + HystrixCommandMetrics.reset(); + HystrixThreadPoolMetrics.reset(); + HystrixCollapserMetrics.reset(); + // clear collapsers + HystrixCollapser.reset(); + // clear circuit breakers + HystrixCircuitBreaker.Factory.reset(); + HystrixPlugins.reset(); + HystrixPropertiesFactory.reset(); + currentCommand.set(new ConcurrentStack()); + } + + private static ThreadLocal> currentCommand = new ThreadLocal>() { + @Override + protected ConcurrentStack initialValue() { + return new ConcurrentStack(); + } + }; + + /** + * Allows a thread to query whether it's current point of execution is within the scope of a HystrixCommand. + *

+ * When ExecutionIsolationStrategy is THREAD then this applies to the isolation (child/worker) thread not the calling thread. + *

+ * When ExecutionIsolationStrategy is SEMAPHORE this applies to the calling thread. + * + * @return HystrixCommandKey of current command being executed or null if none. + */ + public static HystrixCommandKey getCurrentThreadExecutingCommand() { + if (currentCommand == null) { + // statics do "interesting" things across classloaders apparently so this can somehow be null ... + return null; + } + return currentCommand.get().peek(); + } + + /** + * + * @return Action0 to perform the same work as `endCurrentThreadExecutingCommand()` but can be done from any thread + */ + /* package */static Action0 startCurrentThreadExecutingCommand(HystrixCommandKey key) { + final ConcurrentStack list = currentCommand.get(); + try { + list.push(key); + } catch (Exception e) { + logger.warn("Unable to record command starting", e); + } + return new Action0() { + + @Override + public void call() { + endCurrentThreadExecutingCommand(list); + } + + }; + } + + /* package */static void endCurrentThreadExecutingCommand() { + endCurrentThreadExecutingCommand(currentCommand.get()); + } + + private static void endCurrentThreadExecutingCommand(ConcurrentStack list) { + try { + if (!list.isEmpty()) { + list.pop(); + } + } catch (NoSuchElementException e) { + // this shouldn't be possible since we check for empty above and this is thread-isolated + logger.debug("No command found to end.", e); + } catch (Exception e) { + logger.warn("Unable to end command.", e); + } + } + + /* package-private */ static int getCommandCount() { + return currentCommand.get().size(); + } + + /** + * Trieber's algorithm for a concurrent stack + * @param + */ + private static class ConcurrentStack { + AtomicReference> top = new AtomicReference>(); + + public void push(E item) { + Node newHead = new Node(item); + Node oldHead; + do { + oldHead = top.get(); + newHead.next = oldHead; + } while (!top.compareAndSet(oldHead, newHead)); + } + + public E pop() { + Node oldHead; + Node newHead; + do { + oldHead = top.get(); + if (oldHead == null) { + return null; + } + newHead = oldHead.next; + } while (!top.compareAndSet(oldHead, newHead)); + return oldHead.item; + } + + public boolean isEmpty() { + return top.get() == null; + } + + public int size() { + int currentSize = 0; + Node current = top.get(); + while (current != null) { + currentSize++; + current = current.next; + } + return currentSize; + } + + public E peek() { + Node eNode = top.get(); + if (eNode == null) { + return null; + } else { + return eNode.item; + } + } + + private class Node { + public final E item; + public Node next; + + public Node(E item) { + this.item = item; + } + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCachedObservable.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCachedObservable.java new file mode 100644 index 0000000..b627542 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCachedObservable.java @@ -0,0 +1,51 @@ +package com.netflix.hystrix; + +import rx.Observable; +import rx.Subscription; +import rx.functions.Action0; +import rx.subjects.ReplaySubject; + +public class HystrixCachedObservable { + protected final Subscription originalSubscription; + protected final Observable cachedObservable; + private volatile int outstandingSubscriptions = 0; + + protected HystrixCachedObservable(final Observable originalObservable) { + ReplaySubject replaySubject = ReplaySubject.create(); + this.originalSubscription = originalObservable + .subscribe(replaySubject); + + this.cachedObservable = replaySubject + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + outstandingSubscriptions--; + if (outstandingSubscriptions == 0) { + originalSubscription.unsubscribe(); + } + } + }) + .doOnSubscribe(new Action0() { + @Override + public void call() { + outstandingSubscriptions++; + } + }); + } + + public static HystrixCachedObservable from(Observable o, AbstractCommand originalCommand) { + return new HystrixCommandResponseFromCache(o, originalCommand); + } + + public static HystrixCachedObservable from(Observable o) { + return new HystrixCachedObservable(o); + } + + public Observable toObservable() { + return cachedObservable; + } + + public void unsubscribe() { + originalSubscription.unsubscribe(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCircuitBreaker.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCircuitBreaker.java new file mode 100644 index 0000000..93c5429 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCircuitBreaker.java @@ -0,0 +1,324 @@ +/** + * Copyright 2012 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 java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts; +import rx.Subscriber; +import rx.Subscription; + +/** + * Circuit-breaker logic that is hooked into {@link HystrixCommand} execution and will stop allowing executions if failures have gone past the defined threshold. + *

+ * The default (and only) implementation will then allow a single retry after a defined sleepWindow until the execution + * succeeds at which point it will again close the circuit and allow executions again. + */ +public interface HystrixCircuitBreaker { + + /** + * Every {@link HystrixCommand} requests asks this if it is allowed to proceed or not. It is idempotent and does + * not modify any internal state, and takes into account the half-open logic which allows some requests through + * after the circuit has been opened + * + * @return boolean whether a request should be permitted + */ + boolean allowRequest(); + + /** + * Whether the circuit is currently open (tripped). + * + * @return boolean state of circuit breaker + */ + boolean isOpen(); + + /** + * Invoked on successful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state. + */ + void markSuccess(); + + /** + * Invoked on unsuccessful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state. + */ + void markNonSuccess(); + + /** + * Invoked at start of command execution to attempt an execution. This is non-idempotent - it may modify internal + * state. + */ + boolean attemptExecution(); + + /** + * @ExcludeFromJavadoc + * @ThreadSafe + */ + class Factory { + // String is HystrixCommandKey.name() (we can't use HystrixCommandKey directly as we can't guarantee it implements hashcode/equals correctly) + private static ConcurrentHashMap circuitBreakersByCommand = new ConcurrentHashMap(); + + /** + * Get the {@link HystrixCircuitBreaker} instance for a given {@link HystrixCommandKey}. + *

+ * This is thread-safe and ensures only 1 {@link HystrixCircuitBreaker} per {@link HystrixCommandKey}. + * + * @param key + * {@link HystrixCommandKey} of {@link HystrixCommand} instance requesting the {@link HystrixCircuitBreaker} + * @param group + * Pass-thru to {@link HystrixCircuitBreaker} + * @param properties + * Pass-thru to {@link HystrixCircuitBreaker} + * @param metrics + * Pass-thru to {@link HystrixCircuitBreaker} + * @return {@link HystrixCircuitBreaker} for {@link HystrixCommandKey} + */ + public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) { + // this should find it for all but the first time + HystrixCircuitBreaker previouslyCached = circuitBreakersByCommand.get(key.name()); + if (previouslyCached != null) { + return previouslyCached; + } + + // if we get here this is the first time so we need to initialize + + // Create and add to the map ... use putIfAbsent to atomically handle the possible race-condition of + // 2 threads hitting this point at the same time and let ConcurrentHashMap provide us our thread-safety + // If 2 threads hit here only one will get added and the other will get a non-null response instead. + HystrixCircuitBreaker cbForCommand = circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreakerImpl(key, group, properties, metrics)); + if (cbForCommand == null) { + // this means the putIfAbsent step just created a new one so let's retrieve and return it + return circuitBreakersByCommand.get(key.name()); + } else { + // this means a race occurred and while attempting to 'put' another one got there before + // and we instead retrieved it and will now return it + return cbForCommand; + } + } + + /** + * Get the {@link HystrixCircuitBreaker} instance for a given {@link HystrixCommandKey} or null if none exists. + * + * @param key + * {@link HystrixCommandKey} of {@link HystrixCommand} instance requesting the {@link HystrixCircuitBreaker} + * @return {@link HystrixCircuitBreaker} for {@link HystrixCommandKey} + */ + public static HystrixCircuitBreaker getInstance(HystrixCommandKey key) { + return circuitBreakersByCommand.get(key.name()); + } + + /** + * Clears all circuit breakers. If new requests come in instances will be recreated. + */ + /* package */static void reset() { + circuitBreakersByCommand.clear(); + } + } + + + /** + * The default production implementation of {@link HystrixCircuitBreaker}. + * + * @ExcludeFromJavadoc + * @ThreadSafe + */ + /* package */class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker { + private final HystrixCommandProperties properties; + private final HystrixCommandMetrics metrics; + + enum Status { + CLOSED, OPEN, HALF_OPEN; + } + + private final AtomicReference status = new AtomicReference(Status.CLOSED); + private final AtomicLong circuitOpened = new AtomicLong(-1); + private final AtomicReference activeSubscription = new AtomicReference(null); + + protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, final HystrixCommandProperties properties, HystrixCommandMetrics metrics) { + this.properties = properties; + this.metrics = metrics; + + //On a timer, this will set the circuit between OPEN/CLOSED as command executions occur + Subscription s = subscribeToStream(); + activeSubscription.set(s); + } + + private Subscription subscribeToStream() { + /* + * This stream will recalculate the OPEN/CLOSED status on every onNext from the health stream + */ + return metrics.getHealthCountsStream() + .observe() + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(HealthCounts hc) { + // check if we are past the statisticalWindowVolumeThreshold + if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) { + // we are not past the minimum volume threshold for the stat window, + // so no change to circuit status. + // if it was CLOSED, it stays CLOSED + // if it was half-open, we need to wait for a successful command execution + // if it was open, we need to wait for sleep window to elapse + } else { + if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) { + //we are not past the minimum error threshold for the stat window, + // so no change to circuit status. + // if it was CLOSED, it stays CLOSED + // if it was half-open, we need to wait for a successful command execution + // if it was open, we need to wait for sleep window to elapse + } else { + // our failure rate is too high, we need to set the state to OPEN + if (status.compareAndSet(Status.CLOSED, Status.OPEN)) { + circuitOpened.set(System.currentTimeMillis()); + } + } + } + } + }); + } + + @Override + public void markSuccess() { + if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) { + //This thread wins the race to close the circuit - it resets the stream to start it over from 0 + metrics.resetStream(); + Subscription previousSubscription = activeSubscription.get(); + if (previousSubscription != null) { + previousSubscription.unsubscribe(); + } + Subscription newSubscription = subscribeToStream(); + activeSubscription.set(newSubscription); + circuitOpened.set(-1L); + } + } + + @Override + public void markNonSuccess() { + if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) { + //This thread wins the race to re-open the circuit - it resets the start time for the sleep window + circuitOpened.set(System.currentTimeMillis()); + } + } + + @Override + public boolean isOpen() { + if (properties.circuitBreakerForceOpen().get()) { + return true; + } + if (properties.circuitBreakerForceClosed().get()) { + return false; + } + return circuitOpened.get() >= 0; + } + + @Override + public boolean allowRequest() { + if (properties.circuitBreakerForceOpen().get()) { + return false; + } + if (properties.circuitBreakerForceClosed().get()) { + return true; + } + if (circuitOpened.get() == -1) { + return true; + } else { + if (status.get().equals(Status.HALF_OPEN)) { + return false; + } else { + return isAfterSleepWindow(); + } + } + } + + private boolean isAfterSleepWindow() { + final long circuitOpenTime = circuitOpened.get(); + final long currentTime = System.currentTimeMillis(); + final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get(); + return currentTime > circuitOpenTime + sleepWindowTime; + } + + @Override + public boolean attemptExecution() { + if (properties.circuitBreakerForceOpen().get()) { + return false; + } + if (properties.circuitBreakerForceClosed().get()) { + return true; + } + if (circuitOpened.get() == -1) { + return true; + } else { + if (isAfterSleepWindow()) { + //only the first request after sleep window should execute + //if the executing command succeeds, the status will transition to CLOSED + //if the executing command fails, the status will transition to OPEN + //if the executing command gets unsubscribed, the status will transition to OPEN + if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) { + return true; + } else { + return false; + } + } else { + return false; + } + } + } + } + + /** + * An implementation of the circuit breaker that does nothing. + * + * @ExcludeFromJavadoc + */ + /* package */static class NoOpCircuitBreaker implements HystrixCircuitBreaker { + + @Override + public boolean allowRequest() { + return true; + } + + @Override + public boolean isOpen() { + return false; + } + + @Override + public void markSuccess() { + + } + + @Override + public void markNonSuccess() { + + } + + @Override + public boolean attemptExecution() { + return true; + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java new file mode 100644 index 0000000..c32063f --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java @@ -0,0 +1,617 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.collapser.CollapserTimer; +import com.netflix.hystrix.collapser.HystrixCollapserBridge; +import com.netflix.hystrix.collapser.RealCollapserTimer; +import com.netflix.hystrix.collapser.RequestCollapser; +import com.netflix.hystrix.collapser.RequestCollapserFactory; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherFactory; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.Scheduler; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.schedulers.Schedulers; +import rx.subjects.ReplaySubject; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; + +/** + * Collapse multiple requests into a single {@link HystrixCommand} execution based on a time window and optionally a max batch size. + *

+ * This allows an object model to have multiple calls to the command that execute/queue many times in a short period (milliseconds) and have them all get batched into a single backend call. + *

+ * Typically the time window is something like 10ms give or take. + *

+ * NOTE: Do NOT retain any state within instances of this class. + *

+ * It must be stateless or else it will be non-deterministic because most instances are discarded while some are retained and become the + * "collapsers" for all the ones that are discarded. + * + * @param + * The type returned from the {@link HystrixCommand} that will be invoked on batch executions. + * @param + * The type returned from this command. + * @param + * The type of the request argument. If multiple arguments are needed, wrap them in another object or a Tuple. + */ +public abstract class HystrixCollapser implements HystrixExecutable, HystrixObservable { + + static final Logger logger = LoggerFactory.getLogger(HystrixCollapser.class); + + private final RequestCollapserFactory collapserFactory; + private final HystrixRequestCache requestCache; + private final HystrixCollapserBridge collapserInstanceWrapper; + private final HystrixCollapserMetrics metrics; + + /** + * The scope of request collapsing. + *

    + *
  • REQUEST: Requests within the scope of a {@link HystrixRequestContext} will be collapsed. + *

    + * Typically this means that requests within a single user-request (ie. HTTP request) are collapsed. No interaction with other user requests. 1 queue per user request. + *

  • + *
  • GLOBAL: Requests from any thread (ie. all HTTP requests) within the JVM will be collapsed. 1 queue for entire app.
  • + *
+ */ + public static enum Scope implements RequestCollapserFactory.Scope { + REQUEST, GLOBAL + } + + /** + * Collapser with default {@link HystrixCollapserKey} derived from the implementing class name and scoped to {@link Scope#REQUEST} and default configuration. + */ + protected HystrixCollapser() { + this(Setter.withCollapserKey(null).andScope(Scope.REQUEST)); + } + + /** + * Collapser scoped to {@link Scope#REQUEST} and default configuration. + * + * @param collapserKey + * {@link HystrixCollapserKey} that identifies this collapser and provides the key used for retrieving properties, request caches, publishing metrics etc. + */ + protected HystrixCollapser(HystrixCollapserKey collapserKey) { + this(Setter.withCollapserKey(collapserKey).andScope(Scope.REQUEST)); + } + + /** + * Construct a {@link HystrixCollapser} with defined {@link Setter} that allows + * injecting property and strategy overrides and other optional arguments. + *

+ * Null values will result in the default being used. + * + * @param setter + * Fluent interface for constructor arguments + */ + protected HystrixCollapser(Setter setter) { + this(setter.collapserKey, setter.scope, new RealCollapserTimer(), setter.propertiesSetter, null); + } + + /* package for tests */ HystrixCollapser(HystrixCollapserKey collapserKey, Scope scope, CollapserTimer timer, HystrixCollapserProperties.Setter propertiesBuilder) { + this(collapserKey, scope, timer, propertiesBuilder, null); + } + + /* package for tests */ HystrixCollapser(HystrixCollapserKey collapserKey, Scope scope, CollapserTimer timer, HystrixCollapserProperties.Setter propertiesBuilder, HystrixCollapserMetrics metrics) { + if (collapserKey == null || collapserKey.name().trim().equals("")) { + String defaultKeyName = getDefaultNameFromClass(getClass()); + collapserKey = HystrixCollapserKey.Factory.asKey(defaultKeyName); + } + + HystrixCollapserProperties properties = HystrixPropertiesFactory.getCollapserProperties(collapserKey, propertiesBuilder); + this.collapserFactory = new RequestCollapserFactory(collapserKey, scope, timer, properties); + this.requestCache = HystrixRequestCache.getInstance(collapserKey, HystrixPlugins.getInstance().getConcurrencyStrategy()); + + if (metrics == null) { + this.metrics = HystrixCollapserMetrics.getInstance(collapserKey, properties); + } else { + this.metrics = metrics; + } + + final HystrixCollapser self = this; + + /* strategy: HystrixMetricsPublisherCollapser */ + HystrixMetricsPublisherFactory.createOrRetrievePublisherForCollapser(collapserKey, this.metrics, properties); + + /** + * Used to pass public method invocation to the underlying implementation in a separate package while leaving the methods 'protected' in this class. + */ + collapserInstanceWrapper = new HystrixCollapserBridge() { + + @Override + public Collection>> shardRequests(Collection> requests) { + Collection>> shards = self.shardRequests(requests); + self.metrics.markShards(shards.size()); + return shards; + } + + @Override + public Observable createObservableCommand(Collection> requests) { + final HystrixCommand command = self.createCommand(requests); + + command.markAsCollapsedCommand(this.getCollapserKey(), requests.size()); + self.metrics.markBatch(requests.size()); + + return command.toObservable(); + } + + @Override + public Observable mapResponseToRequests(Observable batchResponse, final Collection> requests) { + return batchResponse.single().doOnNext(new Action1() { + @Override + public void call(BatchReturnType batchReturnType) { + // this is a blocking call in HystrixCollapser + self.mapResponseToRequests(batchReturnType, requests); + } + }).ignoreElements().cast(Void.class); + } + + @Override + public HystrixCollapserKey getCollapserKey() { + return self.getCollapserKey(); + } + + }; + } + + private HystrixCollapserProperties getProperties() { + return collapserFactory.getProperties(); + } + + /** + * Key of the {@link HystrixCollapser} used for properties, metrics, caches, reporting etc. + * + * @return {@link HystrixCollapserKey} identifying this {@link HystrixCollapser} instance + */ + public HystrixCollapserKey getCollapserKey() { + return collapserFactory.getCollapserKey(); + } + + /** + * Scope of collapsing. + *

+ *

    + *
  • REQUEST: Requests within the scope of a {@link HystrixRequestContext} will be collapsed. + *

    + * Typically this means that requests within a single user-request (ie. HTTP request) are collapsed. No interaction with other user requests. 1 queue per user request. + *

  • + *
  • GLOBAL: Requests from any thread (ie. all HTTP requests) within the JVM will be collapsed. 1 queue for entire app.
  • + *
+ *

+ * Default: {@link Scope#REQUEST} (defined via constructor) + * + * @return {@link Scope} that collapsing should be performed within. + */ + public Scope getScope() { + return Scope.valueOf(collapserFactory.getScope().name()); + } + + /** + * Return the {@link HystrixCollapserMetrics} for this collapser + * @return {@link HystrixCollapserMetrics} for this collapser + */ + public HystrixCollapserMetrics getMetrics() { + return metrics; + } + + /** + * The request arguments to be passed to the {@link HystrixCommand}. + *

+ * Typically this means to take the argument(s) provided to the constructor and return it here. + *

+ * If there are multiple arguments that need to be bundled, create a single object to contain them, or use a Tuple. + * + * @return RequestArgumentType + */ + public abstract RequestArgumentType getRequestArgument(); + + /** + * Factory method to create a new {@link HystrixCommand}{@code } command object each time a batch needs to be executed. + *

+ * Do not return the same instance each time. Return a new instance on each invocation. + *

+ * Process the 'requests' argument into the arguments the command object needs to perform its work. + *

+ * If a batch or requests needs to be split (sharded) into multiple commands, see {@link #shardRequests}

+ * IMPLEMENTATION NOTE: Be fast (ie. <1ms) in this method otherwise it can block the Timer from executing subsequent batches. Do not do any processing beyond constructing the command and returning + * it. + * + * @param requests + * {@code Collection>} containing {@link CollapsedRequest} objects containing the arguments of each request collapsed in this batch. + * @return {@link HystrixCommand}{@code } which when executed will retrieve results for the batch of arguments as found in the Collection of {@link CollapsedRequest} objects + */ + protected abstract HystrixCommand createCommand(Collection> requests); + + /** + * Override to split (shard) a batch of requests into multiple batches that will each call createCommand separately. + *

+ * The purpose of this is to allow collapsing to work for services that have sharded backends and batch executions that need to be shard-aware. + *

+ * For example, a batch of 100 requests could be split into 4 different batches sharded on name (ie. a-g, h-n, o-t, u-z) that each result in a separate {@link HystrixCommand} being created and + * executed for them. + *

+ * By default this method does nothing to the Collection and is a pass-thru. + * + * @param requests + * {@code Collection>} containing {@link CollapsedRequest} objects containing the arguments of each request collapsed in this batch. + * @return Collection of {@code Collection>} objects sharded according to business rules. + *

The CollapsedRequest instances should not be modified or wrapped as the CollapsedRequest instance object contains state information needed to complete the execution. + */ + protected Collection>> shardRequests(Collection> requests) { + return Collections.singletonList(requests); + } + + /** + * Executed after the {@link HystrixCommand}{@code } command created by {@link #createCommand} finishes processing (unless it fails) for mapping the {@code } to + * the list of {@code CollapsedRequest} objects. + *

+ * IMPORTANT IMPLEMENTATION DETAIL => The expected contract (responsibilities) of this method implementation is: + *

+ *

    + *
  • ALL {@link CollapsedRequest} objects must have either a response or exception set on them even if the response is NULL + * otherwise the user thread waiting on the response will think a response was never received and will either block indefinitely or timeout while waiting.
  • + *
      + *
    • Setting a response is done via {@link CollapsedRequest#setResponse(Object)}
    • + *
    • Setting an exception is done via {@link CollapsedRequest#setException(Exception)}
    • + *
    + *
+ *

+ * Common code when {@code } is {@code List} is: + *

+ * + *

+     * int count = 0;
+     * for ({@code CollapsedRequest} request : requests) {
+     *      request.setResponse(batchResponse.get(count++));
+     * }
+     * 
+ * + * For example if the types were {@code , String, String>}: + *

+ * + *

+     * int count = 0;
+     * for ({@code CollapsedRequest} request : requests) {
+     *      request.setResponse(batchResponse.get(count++));
+     * }
+     * 
+ * + * @param batchResponse + * The {@code } returned from the {@link HystrixCommand}{@code } command created by {@link #createCommand}. + *

+ * + * @param requests + * {@code Collection>} containing {@link CollapsedRequest} objects containing the arguments of each request collapsed in this batch. + *

+ * The {@link CollapsedRequest#setResponse(Object)} or {@link CollapsedRequest#setException(Exception)} must be called on each {@link CollapsedRequest} in the Collection. + */ + protected abstract void mapResponseToRequests(BatchReturnType batchResponse, Collection> requests); + + /** + * Used for asynchronous execution with a callback by subscribing to the {@link Observable}. + *

+ * This eagerly starts execution the same as {@link #queue()} and {@link #execute()}. + * A lazy {@link Observable} can be obtained from {@link #toObservable()}. + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#computation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ * Use {@link #toObservable(rx.Scheduler)} to schedule the callback differently. + *

+ * See https://github.com/Netflix/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of of {@link HystrixCommand}{@code } execution after passing through {@link #mapResponseToRequests} + * to transform the {@code } into {@code } + */ + public Observable observe() { + // use a ReplaySubject to buffer the eagerly subscribed-to Observable + ReplaySubject subject = ReplaySubject.create(); + // eagerly kick off subscription + final Subscription underlyingSubscription = toObservable().subscribe(subject); + // return the subject that can be subscribed to later while the execution has already started + return subject.doOnUnsubscribe(new Action0() { + @Override + public void call() { + underlyingSubscription.unsubscribe(); + } + }); + } + + /** + * A lazy {@link Observable} that will execute when subscribed to. + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#computation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ *

+ * See https://github.com/Netflix/RxJava/wiki for more information. + * + * @return {@code Observable} that lazily executes and calls back with the result of of {@link HystrixCommand}{@code } execution after passing through + * {@link #mapResponseToRequests} to transform the {@code } into {@code } + */ + public Observable toObservable() { + // when we callback with the data we want to do the work + // on a separate thread than the one giving us the callback + return toObservable(Schedulers.computation()); + } + + /** + * A lazy {@link Observable} that will execute when subscribed to. + *

+ * See https://github.com/Netflix/RxJava/wiki for more information. + * + * @param observeOn + * The {@link Scheduler} to execute callbacks on. + * @return {@code Observable} that lazily executes and calls back with the result of of {@link HystrixCommand}{@code } execution after passing through + * {@link #mapResponseToRequests} to transform the {@code } into {@code } + */ + public Observable toObservable(Scheduler observeOn) { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + final boolean isRequestCacheEnabled = getProperties().requestCacheEnabled().get(); + final String cacheKey = getCacheKey(); + + /* try from cache first */ + if (isRequestCacheEnabled) { + HystrixCachedObservable fromCache = requestCache.get(cacheKey); + if (fromCache != null) { + metrics.markResponseFromCache(); + return fromCache.toObservable(); + } + } + + RequestCollapser requestCollapser = collapserFactory.getRequestCollapser(collapserInstanceWrapper); + Observable response = requestCollapser.submitRequest(getRequestArgument()); + + if (isRequestCacheEnabled && cacheKey != null) { + HystrixCachedObservable toCache = HystrixCachedObservable.from(response); + HystrixCachedObservable fromCache = requestCache.putIfAbsent(cacheKey, toCache); + if (fromCache == null) { + return toCache.toObservable(); + } else { + toCache.unsubscribe(); + return fromCache.toObservable(); + } + } + return response; + } + }); + } + + /** + * Used for synchronous execution. + *

+ * If {@link Scope#REQUEST} is being used then synchronous execution will only result in collapsing if other threads are running within the same scope. + * + * @return ResponseType + * Result of {@link HystrixCommand}{@code } execution after passing through {@link #mapResponseToRequests} to transform the {@code } into + * {@code } + * @throws HystrixRuntimeException + * if an error occurs and a fallback cannot be retrieved + */ + public ResponseType execute() { + try { + return queue().get(); + } catch (Throwable e) { + if (e instanceof HystrixRuntimeException) { + throw (HystrixRuntimeException) e; + } + // if we have an exception we know about we'll throw it directly without the threading wrapper exception + if (e.getCause() instanceof HystrixRuntimeException) { + throw (HystrixRuntimeException) e.getCause(); + } + // we don't know what kind of exception this is so create a generic message and throw a new HystrixRuntimeException + String message = getClass().getSimpleName() + " HystrixCollapser failed while executing."; + logger.debug(message, e); // debug only since we're throwing the exception and someone higher will do something with it + //TODO should this be made a HystrixRuntimeException? + throw new RuntimeException(message, e); + } + } + + /** + * Used for asynchronous execution. + *

+ * This will queue up the command and return a Future to get the result once it completes. + * + * @return ResponseType + * Result of {@link HystrixCommand}{@code } execution after passing through {@link #mapResponseToRequests} to transform the {@code } into + * {@code } + * @throws HystrixRuntimeException + * within an ExecutionException.getCause() (thrown by {@link Future#get}) if an error occurs and a fallback cannot be retrieved + */ + public Future queue() { + return toObservable() + .toBlocking() + .toFuture(); + } + + /** + * Key to be used for request caching. + *

+ * By default this returns null which means "do not cache". + *

+ * To enable caching override this method and return a string key uniquely representing the state of a command instance. + *

+ * If multiple command instances in the same request scope match keys then only the first will be executed and all others returned from cache. + * + * @return String cacheKey or null if not to cache + */ + protected String getCacheKey() { + return null; + } + + /** + * Clears all state. If new requests come in instances will be recreated and metrics started from scratch. + */ + /* package */static void reset() { + RequestCollapserFactory.reset(); + } + + private static String getDefaultNameFromClass(@SuppressWarnings("rawtypes") Class cls) { + String fromCache = defaultNameCache.get(cls); + if (fromCache != null) { + return fromCache; + } + // generate the default + // default HystrixCommandKey to use if the method is not overridden + String name = cls.getSimpleName(); + if (name.equals("")) { + // we don't have a SimpleName (anonymous inner class) so use the full class name + name = cls.getName(); + name = name.substring(name.lastIndexOf('.') + 1, name.length()); + } + defaultNameCache.put(cls, name); + return name; + } + + /** + * A request argument RequestArgumentType that was collapsed for batch processing and needs a response ResponseType set on it by the executeBatch implementation. + */ + public interface CollapsedRequest { + /** + * The request argument passed into the {@link HystrixCollapser} instance constructor which was then collapsed. + * + * @return RequestArgumentType + */ + RequestArgumentType getArgument(); + + /** + * This corresponds in a OnNext(Response); OnCompleted pair of emissions. It represents a single-value usecase. + * + * @throws IllegalStateException + * if called more than once or after setException/setComplete. + * @param response + * ResponseType + */ + void setResponse(ResponseType response); + + /** + * When invoked, any Observer will be OnNexted this value + * @throws IllegalStateException + * if called after setException/setResponse/setComplete. + * @param response + */ + void emitResponse(ResponseType response); + + /** + * When set, any Observer will be OnErrored this exception + * + * @param exception exception to set on response + * @throws IllegalStateException + * if called more than once or after setResponse/setComplete. + */ + void setException(Exception exception); + + /** + * When set, any Observer will have an OnCompleted emitted. + * The intent is to use if after a series of emitResponses + * + * Note that, unlike the other 3 methods above, this method does not throw an IllegalStateException. + * This allows Hystrix-core to unilaterally call it without knowing the internal state. + */ + void setComplete(); + } + + /** + * Fluent interface for arguments to the {@link HystrixCollapser} constructor. + *

+ * The required arguments are set via the 'with' factory method and optional arguments via the 'and' chained methods. + *

+ * Example: + *

 {@code
+     *  Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("CollapserName"))
+                .andScope(Scope.REQUEST);
+     * } 
+ * + * @NotThreadSafe + */ + public static class Setter { + private final HystrixCollapserKey collapserKey; + private Scope scope = Scope.REQUEST; // default if nothing is set + private HystrixCollapserProperties.Setter propertiesSetter; + + private Setter(HystrixCollapserKey collapserKey) { + this.collapserKey = collapserKey; + } + + /** + * Setter factory method containing required values. + *

+ * All optional arguments can be set via the chained methods. + * + * @param collapserKey + * {@link HystrixCollapserKey} that identifies this collapser and provides the key used for retrieving properties, request caches, publishing metrics etc. + * @return Setter for fluent interface via method chaining + */ + public static Setter withCollapserKey(HystrixCollapserKey collapserKey) { + return new Setter(collapserKey); + } + + /** + * {@link Scope} defining what scope the collapsing should occur within + * + * @param scope + * + * @return Setter for fluent interface via method chaining + */ + public Setter andScope(Scope scope) { + this.scope = scope; + return this; + } + + /** + * @param propertiesSetter + * {@link HystrixCollapserProperties.Setter} that allows instance specific property overrides (which can then be overridden by dynamic properties, see + * {@link HystrixPropertiesStrategy} for + * information on order of precedence). + *

+ * Will use defaults if left NULL. + * @return Setter for fluent interface via method chaining + */ + public Setter andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter propertiesSetter) { + this.propertiesSetter = propertiesSetter; + return this; + } + + } + + // this is a micro-optimization but saves about 1-2microseconds (on 2011 MacBook Pro) + // on the repetitive string processing that will occur on the same classes over and over again + @SuppressWarnings("rawtypes") + private static ConcurrentHashMap, String> defaultNameCache = new ConcurrentHashMap, String>(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserKey.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserKey.java new file mode 100644 index 0000000..4734cf9 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserKey.java @@ -0,0 +1,75 @@ +/** + * Copyright 2012 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 java.util.concurrent.ConcurrentHashMap; + +/** + * A key to represent a {@link HystrixCollapser} for monitoring, circuit-breakers, metrics publishing, caching and other such uses. + *

+ * This interface is intended to work natively with Enums so that implementing code can be an enum that implements this interface. + */ +public interface HystrixCollapserKey { + + /** + * The word 'name' is used instead of 'key' so that Enums can implement this interface and it work natively. + * + * @return String + */ + public String name(); + + public static class Factory { + + private Factory() { + } + + // used to intern instances so we don't keep re-creating them millions of times for the same key + private static ConcurrentHashMap intern = new ConcurrentHashMap(); + + /** + * Retrieve (or create) an interned HystrixCollapserKey instance for a given name. + * + * @param name collapser name + * @return HystrixCollapserKey instance that is interned (cached) so a given name will always retrieve the same instance. + */ + public static HystrixCollapserKey asKey(String name) { + HystrixCollapserKey k = intern.get(name); + if (k == null) { + intern.putIfAbsent(name, new HystrixCollapserKeyDefault(name)); + } + return intern.get(name); + } + + private static class HystrixCollapserKeyDefault implements HystrixCollapserKey { + + private String name; + + private HystrixCollapserKeyDefault(String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public String toString() { + return name; + } + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserMetrics.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserMetrics.java new file mode 100644 index 0000000..63ace48 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserMetrics.java @@ -0,0 +1,208 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.metric.HystrixCollapserEvent; +import com.netflix.hystrix.metric.HystrixThreadEventStream; +import com.netflix.hystrix.metric.consumer.CumulativeCollapserEventCounterStream; +import com.netflix.hystrix.metric.consumer.RollingCollapserBatchSizeDistributionStream; +import com.netflix.hystrix.metric.consumer.RollingCollapserEventCounterStream; +import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func2; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Used by {@link HystrixCollapser} to record metrics. + * {@link HystrixEventNotifier} not hooked up yet. It may be in the future. + */ +public class HystrixCollapserMetrics extends HystrixMetrics { + + @SuppressWarnings("unused") + private static final Logger logger = LoggerFactory.getLogger(HystrixCollapserMetrics.class); + + // String is HystrixCollapserKey.name() (we can't use HystrixCollapserKey directly as we can't guarantee it implements hashcode/equals correctly) + private static final ConcurrentHashMap metrics = new ConcurrentHashMap(); + + /** + * Get or create the {@link HystrixCollapserMetrics} instance for a given {@link HystrixCollapserKey}. + *

+ * This is thread-safe and ensures only 1 {@link HystrixCollapserMetrics} per {@link HystrixCollapserKey}. + * + * @param key + * {@link HystrixCollapserKey} of {@link HystrixCollapser} instance requesting the {@link HystrixCollapserMetrics} + * @return {@link HystrixCollapserMetrics} + */ + public static HystrixCollapserMetrics getInstance(HystrixCollapserKey key, HystrixCollapserProperties properties) { + // attempt to retrieve from cache first + HystrixCollapserMetrics collapserMetrics = metrics.get(key.name()); + if (collapserMetrics != null) { + return collapserMetrics; + } + // it doesn't exist so we need to create it + collapserMetrics = new HystrixCollapserMetrics(key, properties); + // attempt to store it (race other threads) + HystrixCollapserMetrics existing = metrics.putIfAbsent(key.name(), collapserMetrics); + if (existing == null) { + // we won the thread-race to store the instance we created + return collapserMetrics; + } else { + // we lost so return 'existing' and let the one we created be garbage collected + return existing; + } + } + + /** + * All registered instances of {@link HystrixCollapserMetrics} + * + * @return {@code Collection} + */ + public static Collection getInstances() { + return Collections.unmodifiableCollection(metrics.values()); + } + + private static final HystrixEventType.Collapser[] ALL_EVENT_TYPES = HystrixEventType.Collapser.values(); + + public static final Func2 appendEventToBucket = new Func2() { + @Override + public long[] call(long[] initialCountArray, HystrixCollapserEvent collapserEvent) { + HystrixEventType.Collapser eventType = collapserEvent.getEventType(); + int count = collapserEvent.getCount(); + initialCountArray[eventType.ordinal()] += count; + return initialCountArray; + } + }; + + public static final Func2 bucketAggregator = new Func2() { + @Override + public long[] call(long[] cumulativeEvents, long[] bucketEventCounts) { + for (HystrixEventType.Collapser eventType: ALL_EVENT_TYPES) { + cumulativeEvents[eventType.ordinal()] += bucketEventCounts[eventType.ordinal()]; + } + return cumulativeEvents; + } + }; + + /** + * Clears all state from metrics. If new requests come in instances will be recreated and metrics started from scratch. + */ + /* package */ static void reset() { + metrics.clear(); + } + + private final HystrixCollapserKey collapserKey; + private final HystrixCollapserProperties properties; + + private final RollingCollapserEventCounterStream rollingCollapserEventCounterStream; + private final CumulativeCollapserEventCounterStream cumulativeCollapserEventCounterStream; + private final RollingCollapserBatchSizeDistributionStream rollingCollapserBatchSizeDistributionStream; + + /* package */HystrixCollapserMetrics(HystrixCollapserKey key, HystrixCollapserProperties properties) { + super(null); + this.collapserKey = key; + this.properties = properties; + + rollingCollapserEventCounterStream = RollingCollapserEventCounterStream.getInstance(key, properties); + cumulativeCollapserEventCounterStream = CumulativeCollapserEventCounterStream.getInstance(key, properties); + rollingCollapserBatchSizeDistributionStream = RollingCollapserBatchSizeDistributionStream.getInstance(key, properties); + } + + /** + * {@link HystrixCollapserKey} these metrics represent. + * + * @return HystrixCollapserKey + */ + public HystrixCollapserKey getCollapserKey() { + return collapserKey; + } + + public HystrixCollapserProperties getProperties() { + return properties; + } + + public long getRollingCount(HystrixEventType.Collapser collapserEventType) { + return rollingCollapserEventCounterStream.getLatest(collapserEventType); + } + + public long getCumulativeCount(HystrixEventType.Collapser collapserEventType) { + return cumulativeCollapserEventCounterStream.getLatest(collapserEventType); + } + + @Override + public long getCumulativeCount(HystrixRollingNumberEvent event) { + return getCumulativeCount(HystrixEventType.Collapser.from(event)); + } + + @Override + public long getRollingCount(HystrixRollingNumberEvent event) { + return getRollingCount(HystrixEventType.Collapser.from(event)); + } + + /** + * Retrieve the batch size for the {@link HystrixCollapser} being invoked at a given percentile. + *

+ * Percentile capture and calculation is configured via {@link HystrixCollapserProperties#metricsRollingStatisticalWindowInMilliseconds()} and other related properties. + * + * @param percentile + * Percentile such as 50, 99, or 99.5. + * @return batch size + */ + public int getBatchSizePercentile(double percentile) { + return rollingCollapserBatchSizeDistributionStream.getLatestPercentile(percentile); + } + + public int getBatchSizeMean() { + return rollingCollapserBatchSizeDistributionStream.getLatestMean(); + } + + /** + * Retrieve the shard size for the {@link HystrixCollapser} being invoked at a given percentile. + *

+ * Percentile capture and calculation is configured via {@link HystrixCollapserProperties#metricsRollingStatisticalWindowInMilliseconds()} and other related properties. + * + * @param percentile + * Percentile such as 50, 99, or 99.5. + * @return batch size + */ + public int getShardSizePercentile(double percentile) { + return 0; + //return rollingCollapserUsageDistributionStream.getLatestBatchSizePercentile(percentile); + } + + public int getShardSizeMean() { + return 0; + //return percentileShardSize.getMean(); + } + + public void markRequestBatched() { + } + + public void markResponseFromCache() { + HystrixThreadEventStream.getInstance().collapserResponseFromCache(collapserKey); + } + + public void markBatch(int batchSize) { + HystrixThreadEventStream.getInstance().collapserBatchExecuted(collapserKey, batchSize); + } + + public void markShards(int numShards) { + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserProperties.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserProperties.java new file mode 100644 index 0000000..54e780f --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserProperties.java @@ -0,0 +1,371 @@ +/** + * Copyright 2012 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 static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forBoolean; +import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forInteger; +import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forString; + +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.HystrixRollingNumber; +import com.netflix.hystrix.util.HystrixRollingPercentile; + +/** + * Properties for instances of {@link HystrixCollapser}. + *

+ * Default implementation of methods uses Archaius (https://github.com/Netflix/archaius) + */ +public abstract class HystrixCollapserProperties { + + /* defaults */ + private static final Integer default_maxRequestsInBatch = Integer.MAX_VALUE; + private static final Integer default_timerDelayInMilliseconds = 10; + private static final Boolean default_requestCacheEnabled = true; + /* package */ static final Integer default_metricsRollingStatisticalWindow = 10000;// default => statisticalWindow: 10000 = 10 seconds (and default of 10 buckets so each bucket is 1 second) + private static final Integer default_metricsRollingStatisticalWindowBuckets = 10;// default => statisticalWindowBuckets: 10 = 10 buckets in a 10 second window so each bucket is 1 second + private static final Boolean default_metricsRollingPercentileEnabled = true; + private static final Integer default_metricsRollingPercentileWindow = 60000; // default to 1 minute for RollingPercentile + private static final Integer default_metricsRollingPercentileWindowBuckets = 6; // default to 6 buckets (10 seconds each in 60 second window) + private static final Integer default_metricsRollingPercentileBucketSize = 100; // default to 100 values max per bucket + + private final HystrixProperty maxRequestsInBatch; + private final HystrixProperty timerDelayInMilliseconds; + private final HystrixProperty requestCacheEnabled; + private final HystrixProperty metricsRollingStatisticalWindowInMilliseconds; // milliseconds back that will be tracked + private final HystrixProperty metricsRollingStatisticalWindowBuckets; // number of buckets in the statisticalWindow + private final HystrixProperty metricsRollingPercentileEnabled; // Whether monitoring should be enabled + private final HystrixProperty metricsRollingPercentileWindowInMilliseconds; // number of milliseconds that will be tracked in RollingPercentile + private final HystrixProperty metricsRollingPercentileWindowBuckets; // number of buckets percentileWindow will be divided into + private final HystrixProperty metricsRollingPercentileBucketSize; // how many values will be stored in each percentileWindowBucket + + protected HystrixCollapserProperties(HystrixCollapserKey collapserKey) { + this(collapserKey, new Setter(), "hystrix"); + } + + protected HystrixCollapserProperties(HystrixCollapserKey collapserKey, Setter builder) { + this(collapserKey, builder, "hystrix"); + } + + protected HystrixCollapserProperties(HystrixCollapserKey key, Setter builder, String propertyPrefix) { + this.maxRequestsInBatch = getProperty(propertyPrefix, key, "maxRequestsInBatch", builder.getMaxRequestsInBatch(), default_maxRequestsInBatch); + this.timerDelayInMilliseconds = getProperty(propertyPrefix, key, "timerDelayInMilliseconds", builder.getTimerDelayInMilliseconds(), default_timerDelayInMilliseconds); + this.requestCacheEnabled = getProperty(propertyPrefix, key, "requestCache.enabled", builder.getRequestCacheEnabled(), default_requestCacheEnabled); + this.metricsRollingStatisticalWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingStats.timeInMilliseconds", builder.getMetricsRollingStatisticalWindowInMilliseconds(), default_metricsRollingStatisticalWindow); + this.metricsRollingStatisticalWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingStats.numBuckets", builder.getMetricsRollingStatisticalWindowBuckets(), default_metricsRollingStatisticalWindowBuckets); + this.metricsRollingPercentileEnabled = getProperty(propertyPrefix, key, "metrics.rollingPercentile.enabled", builder.getMetricsRollingPercentileEnabled(), default_metricsRollingPercentileEnabled); + this.metricsRollingPercentileWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingPercentile.timeInMilliseconds", builder.getMetricsRollingPercentileWindowInMilliseconds(), default_metricsRollingPercentileWindow); + this.metricsRollingPercentileWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingPercentile.numBuckets", builder.getMetricsRollingPercentileWindowBuckets(), default_metricsRollingPercentileWindowBuckets); + this.metricsRollingPercentileBucketSize = getProperty(propertyPrefix, key, "metrics.rollingPercentile.bucketSize", builder.getMetricsRollingPercentileBucketSize(), default_metricsRollingPercentileBucketSize); + } + + private static HystrixProperty getProperty(String propertyPrefix, HystrixCollapserKey key, String instanceProperty, Integer builderOverrideValue, Integer defaultValue) { + return forInteger() + .add(propertyPrefix + ".collapser." + key.name() + "." + instanceProperty, builderOverrideValue) + .add(propertyPrefix + ".collapser.default." + instanceProperty, defaultValue) + .build(); + + + } + + private static HystrixProperty getProperty(String propertyPrefix, HystrixCollapserKey key, String instanceProperty, Boolean builderOverrideValue, Boolean defaultValue) { + return forBoolean() + .add(propertyPrefix + ".collapser." + key.name() + "." + instanceProperty, builderOverrideValue) + .add(propertyPrefix + ".collapser.default." + instanceProperty, defaultValue) + .build(); + } + + /** + * Whether request caching is enabled for {@link HystrixCollapser#execute} and {@link HystrixCollapser#queue} invocations. + * + * Deprecated as of 1.4.0-RC7 in favor of requestCacheEnabled() (to match {@link HystrixCommandProperties#requestCacheEnabled()} + * + * @return {@code HystrixProperty} + */ + @Deprecated + public HystrixProperty requestCachingEnabled() { + return requestCacheEnabled; + } + + /** + * Whether request caching is enabled for {@link HystrixCollapser#execute} and {@link HystrixCollapser#queue} invocations. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty requestCacheEnabled() { + return requestCacheEnabled; + } + + /** + * The maximum number of requests allowed in a batch before triggering a batch execution. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty maxRequestsInBatch() { + return maxRequestsInBatch; + } + + /** + * The number of milliseconds between batch executions (unless {@link #maxRequestsInBatch} is hit which will cause a batch to execute early. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty timerDelayInMilliseconds() { + return timerDelayInMilliseconds; + } + + /** + * Duration of statistical rolling window in milliseconds. This is passed into {@link HystrixRollingNumber} inside {@link HystrixCommandMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingStatisticalWindowInMilliseconds() { + return metricsRollingStatisticalWindowInMilliseconds; + } + + /** + * Number of buckets the rolling statistical window is broken into. This is passed into {@link HystrixRollingNumber} inside {@link HystrixCollapserMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingStatisticalWindowBuckets() { + return metricsRollingStatisticalWindowBuckets; + } + + /** + * Whether percentile metrics should be captured using {@link HystrixRollingPercentile} inside {@link HystrixCollapserMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingPercentileEnabled() { + return metricsRollingPercentileEnabled; + } + + /** + * Duration of percentile rolling window in milliseconds. This is passed into {@link HystrixRollingPercentile} inside {@link HystrixCollapserMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingPercentileWindowInMilliseconds() { + return metricsRollingPercentileWindowInMilliseconds; + } + + /** + * Number of buckets the rolling percentile window is broken into. This is passed into {@link HystrixRollingPercentile} inside {@link HystrixCollapserMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingPercentileWindowBuckets() { + return metricsRollingPercentileWindowBuckets; + } + + /** + * Maximum number of values stored in each bucket of the rolling percentile. This is passed into {@link HystrixRollingPercentile} inside {@link HystrixCollapserMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingPercentileBucketSize() { + return metricsRollingPercentileBucketSize; + } + + /** + * Factory method to retrieve the default Setter. + */ + public static Setter Setter() { + return new Setter(); + } + + /** + * Factory method to retrieve the default Setter. + * Groovy has a bug (GROOVY-6286) which does not allow method names and inner classes to have the same name + * This method fixes Issue #967 and allows Groovy consumers to choose this method and not trigger the bug + */ + public static Setter defaultSetter() { + return Setter(); + } + + /** + * Fluent interface that allows chained setting of properties that can be passed into a {@link HystrixCollapser} constructor to inject instance specific property overrides. + *

+ * See {@link HystrixPropertiesStrategy} for more information on order of precedence. + *

+ * Example: + *

+ *

 {@code
+     * HystrixCollapserProperties.Setter()
+     *           .setMaxRequestsInBatch(100)
+     *           .setTimerDelayInMilliseconds(10);
+     * } 
+ * + * @NotThreadSafe + */ + public static class Setter { + @Deprecated private Boolean collapsingEnabled = null; + private Integer maxRequestsInBatch = null; + private Integer timerDelayInMilliseconds = null; + private Boolean requestCacheEnabled = null; + private Integer metricsRollingStatisticalWindowInMilliseconds = null; + private Integer metricsRollingStatisticalWindowBuckets = null; + private Integer metricsRollingPercentileBucketSize = null; + private Boolean metricsRollingPercentileEnabled = null; + private Integer metricsRollingPercentileWindowInMilliseconds = null; + private Integer metricsRollingPercentileWindowBuckets = null; + + private Setter() { + } + + /** + * Deprecated because the collapsingEnabled setting doesn't do anything. + */ + @Deprecated + public Boolean getCollapsingEnabled() { + return collapsingEnabled; + } + + public Integer getMaxRequestsInBatch() { + return maxRequestsInBatch; + } + + public Integer getTimerDelayInMilliseconds() { + return timerDelayInMilliseconds; + } + + public Boolean getRequestCacheEnabled() { + return requestCacheEnabled; + } + + public Integer getMetricsRollingStatisticalWindowInMilliseconds() { + return metricsRollingStatisticalWindowInMilliseconds; + } + + public Integer getMetricsRollingStatisticalWindowBuckets() { + return metricsRollingStatisticalWindowBuckets; + } + + public Integer getMetricsRollingPercentileBucketSize() { + return metricsRollingPercentileBucketSize; + } + + public Boolean getMetricsRollingPercentileEnabled() { + return metricsRollingPercentileEnabled; + } + + public Integer getMetricsRollingPercentileWindowInMilliseconds() { + return metricsRollingPercentileWindowInMilliseconds; + } + + public Integer getMetricsRollingPercentileWindowBuckets() { + return metricsRollingPercentileWindowBuckets; + } + + /** + * Deprecated because the collapsingEnabled setting doesn't do anything. + */ + @Deprecated + public Setter withCollapsingEnabled(boolean value) { + this.collapsingEnabled = value; + return this; + } + + public Setter withMaxRequestsInBatch(int value) { + this.maxRequestsInBatch = value; + return this; + } + + public Setter withTimerDelayInMilliseconds(int value) { + this.timerDelayInMilliseconds = value; + return this; + } + + public Setter withRequestCacheEnabled(boolean value) { + this.requestCacheEnabled = value; + return this; + } + + public Setter withMetricsRollingStatisticalWindowInMilliseconds(int value) { + this.metricsRollingStatisticalWindowInMilliseconds = value; + return this; + } + + public Setter withMetricsRollingStatisticalWindowBuckets(int value) { + this.metricsRollingStatisticalWindowBuckets = value; + return this; + } + + public Setter withMetricsRollingPercentileBucketSize(int value) { + this.metricsRollingPercentileBucketSize = value; + return this; + } + + public Setter withMetricsRollingPercentileEnabled(boolean value) { + this.metricsRollingPercentileEnabled = value; + return this; + } + + public Setter withMetricsRollingPercentileWindowInMilliseconds(int value) { + this.metricsRollingPercentileWindowInMilliseconds = value; + return this; + } + + public Setter withMetricsRollingPercentileWindowBuckets(int value) { + this.metricsRollingPercentileWindowBuckets = value; + return this; + } + + /** + * Base properties for unit testing. + */ + /* package */static Setter getUnitTestPropertiesBuilder() { + return new Setter() + .withMaxRequestsInBatch(Integer.MAX_VALUE) + .withTimerDelayInMilliseconds(10) + .withRequestCacheEnabled(true); + } + + /** + * Return a static representation of the properties with values from the Builder so that UnitTests can create properties that are not affected by the actual implementations which pick up their + * values dynamically. + * + * @param builder collapser properties builder + * @return HystrixCollapserProperties + */ + /* package */static HystrixCollapserProperties asMock(final Setter builder) { + return new HystrixCollapserProperties(TestHystrixCollapserKey.TEST) { + + @Override + public HystrixProperty requestCachingEnabled() { + return HystrixProperty.Factory.asProperty(builder.requestCacheEnabled); + } + + @Override + public HystrixProperty maxRequestsInBatch() { + return HystrixProperty.Factory.asProperty(builder.maxRequestsInBatch); + } + + @Override + public HystrixProperty timerDelayInMilliseconds() { + return HystrixProperty.Factory.asProperty(builder.timerDelayInMilliseconds); + } + + }; + } + + private static enum TestHystrixCollapserKey implements HystrixCollapserKey { + TEST + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java new file mode 100644 index 0000000..dba1e33 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java @@ -0,0 +1,489 @@ +/** + * Copyright 2012 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 java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import com.netflix.hystrix.util.Exceptions; +import rx.Observable; +import rx.functions.Action0; + +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import rx.functions.Func0; + +/** + * Used to wrap code that will execute potentially risky functionality (typically meaning a service call over the network) + * with fault and latency tolerance, statistics and performance metrics capture, circuit breaker and bulkhead functionality. + * This command is essentially a blocking command but provides an Observable facade if used with observe() + * + * @param + * the return type + * + * @ThreadSafe + */ +public abstract class HystrixCommand extends AbstractCommand implements HystrixExecutable, HystrixInvokableInfo, HystrixObservable { + + + /** + * Construct a {@link HystrixCommand} with defined {@link HystrixCommandGroupKey}. + *

+ * The {@link HystrixCommandKey} will be derived from the implementing class name. + * + * @param group + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interact with, + * common business purpose etc. + */ + protected HystrixCommand(HystrixCommandGroupKey group) { + super(group, null, null, null, null, null, null, null, null, null, null, null); + } + + + /** + * Construct a {@link HystrixCommand} with defined {@link HystrixCommandGroupKey} and {@link HystrixThreadPoolKey}. + *

+ * The {@link HystrixCommandKey} will be derived from the implementing class name. + * + * @param group + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interact with, + * common business purpose etc. + * @param threadPool + * {@link HystrixThreadPoolKey} used to identify the thread pool in which a {@link HystrixCommand} executes. + */ + protected HystrixCommand(HystrixCommandGroupKey group, HystrixThreadPoolKey threadPool) { + super(group, null, threadPool, null, null, null, null, null, null, null, null, null); + } + + /** + * Construct a {@link HystrixCommand} with defined {@link HystrixCommandGroupKey} and thread timeout + *

+ * The {@link HystrixCommandKey} will be derived from the implementing class name. + * + * @param group + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interact with, + * common business purpose etc. + * @param executionIsolationThreadTimeoutInMilliseconds + * Time in milliseconds at which point the calling thread will timeout (using {@link Future#get}) and walk away from the executing thread. + */ + protected HystrixCommand(HystrixCommandGroupKey group, int executionIsolationThreadTimeoutInMilliseconds) { + super(group, null, null, null, null, HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(executionIsolationThreadTimeoutInMilliseconds), null, null, null, null, null, null); + } + + /** + * Construct a {@link HystrixCommand} with defined {@link HystrixCommandGroupKey}, {@link HystrixThreadPoolKey}, and thread timeout. + *

+ * The {@link HystrixCommandKey} will be derived from the implementing class name. + * + * @param group + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interact with, + * common business purpose etc. + * @param threadPool + * {@link HystrixThreadPool} used to identify the thread pool in which a {@link HystrixCommand} executes. + * @param executionIsolationThreadTimeoutInMilliseconds + * Time in milliseconds at which point the calling thread will timeout (using {@link Future#get}) and walk away from the executing thread. + */ + protected HystrixCommand(HystrixCommandGroupKey group, HystrixThreadPoolKey threadPool, int executionIsolationThreadTimeoutInMilliseconds) { + super(group, null, threadPool, null, null, HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(executionIsolationThreadTimeoutInMilliseconds), null, null, null, null, null, null); + } + + /** + * Construct a {@link HystrixCommand} with defined {@link Setter} that allows injecting property and strategy overrides and other optional arguments. + *

+ * NOTE: The {@link HystrixCommandKey} is used to associate a {@link HystrixCommand} with {@link HystrixCircuitBreaker}, {@link HystrixCommandMetrics} and other objects. + *

+ * Do not create multiple {@link HystrixCommand} implementations with the same {@link HystrixCommandKey} but different injected default properties as the first instantiated will win. + *

+ * Properties passed in via {@link Setter#andCommandPropertiesDefaults} or {@link Setter#andThreadPoolPropertiesDefaults} are cached for the given {@link HystrixCommandKey} for the life of the JVM + * or until {@link Hystrix#reset()} is called. Dynamic properties allow runtime changes. Read more on the Hystrix Wiki. + * + * @param setter + * Fluent interface for constructor arguments + */ + protected HystrixCommand(Setter setter) { + // use 'null' to specify use the default + this(setter.groupKey, setter.commandKey, setter.threadPoolKey, null, null, setter.commandPropertiesDefaults, setter.threadPoolPropertiesDefaults, null, null, null, null, null); + } + + /** + * Allow constructing a {@link HystrixCommand} with injection of most aspects of its functionality. + *

+ * Some of these never have a legitimate reason for injection except in unit testing. + *

+ * Most of the args will revert to a valid default if 'null' is passed in. + */ + /* package for testing */HystrixCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, + HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults, + HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore, + HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { + super(group, key, threadPoolKey, circuitBreaker, threadPool, commandPropertiesDefaults, threadPoolPropertiesDefaults, metrics, fallbackSemaphore, executionSemaphore, propertiesStrategy, executionHook); + } + + /** + * Fluent interface for arguments to the {@link HystrixCommand} constructor. + *

+ * The required arguments are set via the 'with' factory method and optional arguments via the 'and' chained methods. + *

+ * Example: + *

 {@code
+     *  Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GroupName"))
+                .andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"));
+     * } 
+ * + * @NotThreadSafe + */ + final public static class Setter { + + protected final HystrixCommandGroupKey groupKey; + protected HystrixCommandKey commandKey; + protected HystrixThreadPoolKey threadPoolKey; + protected HystrixCommandProperties.Setter commandPropertiesDefaults; + protected HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults; + + /** + * Setter factory method containing required values. + *

+ * All optional arguments can be set via the chained methods. + * + * @param groupKey + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace + * with, + * common business purpose etc. + */ + protected Setter(HystrixCommandGroupKey groupKey) { + this.groupKey = groupKey; + } + + /** + * Setter factory method with required values. + *

+ * All optional arguments can be set via the chained methods. + * + * @param groupKey + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace + * with, + * common business purpose etc. + */ + public static Setter withGroupKey(HystrixCommandGroupKey groupKey) { + return new Setter(groupKey); + } + + /** + * @param commandKey + * {@link HystrixCommandKey} used to identify a {@link HystrixCommand} instance for statistics, circuit-breaker, properties, etc. + *

+ * By default this will be derived from the instance class name. + *

+ * NOTE: Every unique {@link HystrixCommandKey} will result in new instances of {@link HystrixCircuitBreaker}, {@link HystrixCommandMetrics} and {@link HystrixCommandProperties}. + * Thus, + * the number of variants should be kept to a finite and reasonable number to avoid high-memory usage or memory leaks. + *

+ * Hundreds of keys is fine, tens of thousands is probably not. + * @return Setter for fluent interface via method chaining + */ + public Setter andCommandKey(HystrixCommandKey commandKey) { + this.commandKey = commandKey; + return this; + } + + /** + * @param threadPoolKey + * {@link HystrixThreadPoolKey} used to define which thread-pool this command should run in (when configured to run on separate threads via + * {@link HystrixCommandProperties#executionIsolationStrategy()}). + *

+ * By default this is derived from the {@link HystrixCommandGroupKey} but if injected this allows multiple commands to have the same {@link HystrixCommandGroupKey} but different + * thread-pools. + * @return Setter for fluent interface via method chaining + */ + public Setter andThreadPoolKey(HystrixThreadPoolKey threadPoolKey) { + this.threadPoolKey = threadPoolKey; + return this; + } + + /** + * Optional + * + * @param commandPropertiesDefaults + * {@link HystrixCommandProperties.Setter} with property overrides for this specific instance of {@link HystrixCommand}. + *

+ * See the {@link HystrixPropertiesStrategy} JavaDocs for more information on properties and order of precedence. + * @return Setter for fluent interface via method chaining + */ + public Setter andCommandPropertiesDefaults(HystrixCommandProperties.Setter commandPropertiesDefaults) { + this.commandPropertiesDefaults = commandPropertiesDefaults; + return this; + } + + /** + * Optional + * + * @param threadPoolPropertiesDefaults + * {@link HystrixThreadPoolProperties.Setter} with property overrides for the {@link HystrixThreadPool} used by this specific instance of {@link HystrixCommand}. + *

+ * See the {@link HystrixPropertiesStrategy} JavaDocs for more information on properties and order of precedence. + * @return Setter for fluent interface via method chaining + */ + public Setter andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) { + this.threadPoolPropertiesDefaults = threadPoolPropertiesDefaults; + return this; + } + + } + + private final AtomicReference executionThread = new AtomicReference(); + private final AtomicBoolean interruptOnFutureCancel = new AtomicBoolean(false); + + /** + * Implement this method with code to be executed when {@link #execute()} or {@link #queue()} are invoked. + * + * @return R response type + * @throws Exception + * if command execution fails + */ + protected abstract R run() throws Exception; + + /** + * If {@link #execute()} or {@link #queue()} fails in any way then this method will be invoked to provide an opportunity to return a fallback response. + *

+ * This should do work that does not require network transport to produce. + *

+ * In other words, this should be a static or cached result that can immediately be returned upon failure. + *

+ * If network traffic is wanted for fallback (such as going to MemCache) then the fallback implementation should invoke another {@link HystrixCommand} instance that protects against that network + * access and possibly has another level of fallback that does not involve network access. + *

+ * DEFAULT BEHAVIOR: It throws UnsupportedOperationException. + * + * @return R or throw UnsupportedOperationException if not implemented + */ + protected R getFallback() { + throw new UnsupportedOperationException("No fallback available."); + } + + @Override + final protected Observable getExecutionObservable() { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + try { + return Observable.just(run()); + } catch (Throwable ex) { + return Observable.error(ex); + } + } + }).doOnSubscribe(new Action0() { + @Override + public void call() { + // Save thread on which we get subscribed so that we can interrupt it later if needed + executionThread.set(Thread.currentThread()); + } + }); + } + + @Override + final protected Observable getFallbackObservable() { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + try { + return Observable.just(getFallback()); + } catch (Throwable ex) { + return Observable.error(ex); + } + } + }); + } + + /** + * Used for synchronous execution of command. + * + * @return R + * Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @throws HystrixRuntimeException + * if a failure occurs and a fallback cannot be retrieved + * @throws HystrixBadRequestException + * if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public R execute() { + try { + return queue().get(); + } catch (Exception e) { + throw Exceptions.sneakyThrow(decomposeException(e)); + } + } + + /** + * Used for asynchronous execution of command. + *

+ * This will queue up the command on the thread pool and return an {@link Future} to get the result once it completes. + *

+ * NOTE: If configured to not run in a separate thread, this will have the same effect as {@link #execute()} and will block. + *

+ * We don't throw an exception but just flip to synchronous execution so code doesn't need to change in order to switch a command from running on a separate thread to the calling thread. + * + * @return {@code Future} Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Future.get()} in {@link ExecutionException#getCause()} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Future.get()} in {@link ExecutionException#getCause()} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public Future queue() { + /* + * The Future returned by Observable.toBlocking().toFuture() does not implement the + * interruption of the execution thread when the "mayInterrupt" flag of Future.cancel(boolean) is set to true; + * thus, to comply with the contract of Future, we must wrap around it. + */ + final Future delegate = toObservable().toBlocking().toFuture(); + + final Future f = new Future() { + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + if (delegate.isCancelled()) { + return false; + } + + if (HystrixCommand.this.getProperties().executionIsolationThreadInterruptOnFutureCancel().get()) { + /* + * The only valid transition here is false -> true. If there are two futures, say f1 and f2, created by this command + * (which is super-weird, but has never been prohibited), and calls to f1.cancel(true) and to f2.cancel(false) are + * issued by different threads, it's unclear about what value would be used by the time mayInterruptOnCancel is checked. + * The most consistent way to deal with this scenario is to say that if *any* cancellation is invoked with interruption, + * than that interruption request cannot be taken back. + */ + interruptOnFutureCancel.compareAndSet(false, mayInterruptIfRunning); + } + + final boolean res = delegate.cancel(interruptOnFutureCancel.get()); + + if (!isExecutionComplete() && interruptOnFutureCancel.get()) { + final Thread t = executionThread.get(); + if (t != null && !t.equals(Thread.currentThread())) { + t.interrupt(); + } + } + + return res; + } + + @Override + public boolean isCancelled() { + return delegate.isCancelled(); + } + + @Override + public boolean isDone() { + return delegate.isDone(); + } + + @Override + public R get() throws InterruptedException, ExecutionException { + return delegate.get(); + } + + @Override + public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return delegate.get(timeout, unit); + } + + }; + + /* special handling of error states that throw immediately */ + if (f.isDone()) { + try { + f.get(); + return f; + } catch (Exception e) { + Throwable t = decomposeException(e); + if (t instanceof HystrixBadRequestException) { + return f; + } else if (t instanceof HystrixRuntimeException) { + HystrixRuntimeException hre = (HystrixRuntimeException) t; + switch (hre.getFailureType()) { + case COMMAND_EXCEPTION: + case TIMEOUT: + // we don't throw these types from queue() only from queue().get() as they are execution errors + return f; + default: + // these are errors we throw from queue() as they as rejection type errors + throw hre; + } + } else { + throw Exceptions.sneakyThrow(t); + } + } + } + + return f; + } + + @Override + protected String getFallbackMethodName() { + return "getFallback"; + } + + @Override + protected boolean isFallbackUserDefined() { + Boolean containsFromMap = commandContainsFallback.get(commandKey); + if (containsFromMap != null) { + return containsFromMap; + } else { + Boolean toInsertIntoMap; + try { + getClass().getDeclaredMethod("getFallback"); + toInsertIntoMap = true; + } catch (NoSuchMethodException nsme) { + toInsertIntoMap = false; + } + commandContainsFallback.put(commandKey, toInsertIntoMap); + return toInsertIntoMap; + } + } + + @Override + protected boolean commandIsScalar() { + return true; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandGroupKey.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandGroupKey.java new file mode 100644 index 0000000..896507c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandGroupKey.java @@ -0,0 +1,63 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.util.InternMap; + +/** + * A group name for a {@link HystrixCommand}. This is used for grouping together commands such as for reporting, alerting, dashboards or team/library ownership. + *

+ * By default this will be used to define the {@link HystrixThreadPoolKey} unless a separate one is defined. + *

+ * This interface is intended to work natively with Enums so that implementing code can have an enum with the owners that implements this interface. + */ +public interface HystrixCommandGroupKey extends HystrixKey { + class Factory { + private Factory() { + } + + // used to intern instances so we don't keep re-creating them millions of times for the same key + private static final InternMap intern + = new InternMap( + new InternMap.ValueConstructor() { + @Override + public HystrixCommandGroupDefault create(String key) { + return new HystrixCommandGroupDefault(key); + } + }); + + + /** + * Retrieve (or create) an interned HystrixCommandGroup instance for a given name. + * + * @param name command group name + * @return HystrixCommandGroup instance that is interned (cached) so a given name will always retrieve the same instance. + */ + public static HystrixCommandGroupKey asKey(String name) { + return intern.interned(name); + } + + private static class HystrixCommandGroupDefault extends HystrixKey.HystrixKeyDefault implements HystrixCommandGroupKey { + public HystrixCommandGroupDefault(String name) { + super(name); + } + } + + /* package-private */ static int getGroupCount() { + return intern.size(); + } + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandKey.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandKey.java new file mode 100644 index 0000000..9780598 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandKey.java @@ -0,0 +1,62 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.util.InternMap; + +/** + * A key to represent a {@link HystrixCommand} for monitoring, circuit-breakers, metrics publishing, caching and other such uses. + *

+ * This interface is intended to work natively with Enums so that implementing code can be an enum that implements this interface. + */ +public interface HystrixCommandKey extends HystrixKey { + class Factory { + private Factory() { + } + + // used to intern instances so we don't keep re-creating them millions of times for the same key + private static final InternMap intern + = new InternMap( + new InternMap.ValueConstructor() { + @Override + public HystrixCommandKeyDefault create(String key) { + return new HystrixCommandKeyDefault(key); + } + }); + + + /** + * Retrieve (or create) an interned HystrixCommandKey instance for a given name. + * + * @param name command name + * @return HystrixCommandKey instance that is interned (cached) so a given name will always retrieve the same instance. + */ + public static HystrixCommandKey asKey(String name) { + return intern.interned(name); + } + + private static class HystrixCommandKeyDefault extends HystrixKey.HystrixKeyDefault implements HystrixCommandKey { + public HystrixCommandKeyDefault(String name) { + super(name); + } + } + + /* package-private */ static int getCommandCount() { + return intern.size(); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandMetrics.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandMetrics.java new file mode 100644 index 0000000..433f0cb --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandMetrics.java @@ -0,0 +1,443 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.HystrixThreadEventStream; +import com.netflix.hystrix.metric.consumer.CumulativeCommandEventCounterStream; +import com.netflix.hystrix.metric.consumer.HealthCountsStream; +import com.netflix.hystrix.metric.consumer.RollingCommandEventCounterStream; +import com.netflix.hystrix.metric.consumer.RollingCommandLatencyDistributionStream; +import com.netflix.hystrix.metric.consumer.RollingCommandMaxConcurrencyStream; +import com.netflix.hystrix.metric.consumer.RollingCommandUserLatencyDistributionStream; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; +import rx.functions.Func2; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Used by {@link HystrixCommand} to record metrics. + */ +public class HystrixCommandMetrics extends HystrixMetrics { + + @SuppressWarnings("unused") + private static final Logger logger = LoggerFactory.getLogger(HystrixCommandMetrics.class); + + private static final HystrixEventType[] ALL_EVENT_TYPES = HystrixEventType.values(); + + public static final Func2 appendEventToBucket = new Func2() { + @Override + public long[] call(long[] initialCountArray, HystrixCommandCompletion execution) { + ExecutionResult.EventCounts eventCounts = execution.getEventCounts(); + for (HystrixEventType eventType: ALL_EVENT_TYPES) { + switch (eventType) { + case EXCEPTION_THROWN: break; //this is just a sum of other anyway - don't do the work here + default: + initialCountArray[eventType.ordinal()] += eventCounts.getCount(eventType); + break; + } + } + return initialCountArray; + } + }; + + public static final Func2 bucketAggregator = new Func2() { + @Override + public long[] call(long[] cumulativeEvents, long[] bucketEventCounts) { + for (HystrixEventType eventType: ALL_EVENT_TYPES) { + switch (eventType) { + case EXCEPTION_THROWN: + for (HystrixEventType exceptionEventType: HystrixEventType.EXCEPTION_PRODUCING_EVENT_TYPES) { + cumulativeEvents[eventType.ordinal()] += bucketEventCounts[exceptionEventType.ordinal()]; + } + break; + default: + cumulativeEvents[eventType.ordinal()] += bucketEventCounts[eventType.ordinal()]; + break; + } + } + return cumulativeEvents; + } + }; + + // String is HystrixCommandKey.name() (we can't use HystrixCommandKey directly as we can't guarantee it implements hashcode/equals correctly) + private static final ConcurrentHashMap metrics = new ConcurrentHashMap(); + + /** + * Get or create the {@link HystrixCommandMetrics} instance for a given {@link HystrixCommandKey}. + *

+ * This is thread-safe and ensures only 1 {@link HystrixCommandMetrics} per {@link HystrixCommandKey}. + * + * @param key + * {@link HystrixCommandKey} of {@link HystrixCommand} instance requesting the {@link HystrixCommandMetrics} + * @param commandGroup + * Pass-thru to {@link HystrixCommandMetrics} instance on first time when constructed + * @param properties + * Pass-thru to {@link HystrixCommandMetrics} instance on first time when constructed + * @return {@link HystrixCommandMetrics} + */ + public static HystrixCommandMetrics getInstance(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixCommandProperties properties) { + return getInstance(key, commandGroup, null, properties); + } + + /** + * Get or create the {@link HystrixCommandMetrics} instance for a given {@link HystrixCommandKey}. + *

+ * This is thread-safe and ensures only 1 {@link HystrixCommandMetrics} per {@link HystrixCommandKey}. + * + * @param key + * {@link HystrixCommandKey} of {@link HystrixCommand} instance requesting the {@link HystrixCommandMetrics} + * @param commandGroup + * Pass-thru to {@link HystrixCommandMetrics} instance on first time when constructed + * @param properties + * Pass-thru to {@link HystrixCommandMetrics} instance on first time when constructed + * @return {@link HystrixCommandMetrics} + */ + public static HystrixCommandMetrics getInstance(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixThreadPoolKey threadPoolKey, HystrixCommandProperties properties) { + // attempt to retrieve from cache first + HystrixCommandMetrics commandMetrics = metrics.get(key.name()); + if (commandMetrics != null) { + return commandMetrics; + } else { + synchronized (HystrixCommandMetrics.class) { + HystrixCommandMetrics existingMetrics = metrics.get(key.name()); + if (existingMetrics != null) { + return existingMetrics; + } else { + HystrixThreadPoolKey nonNullThreadPoolKey; + if (threadPoolKey == null) { + nonNullThreadPoolKey = HystrixThreadPoolKey.Factory.asKey(commandGroup.name()); + } else { + nonNullThreadPoolKey = threadPoolKey; + } + HystrixCommandMetrics newCommandMetrics = new HystrixCommandMetrics(key, commandGroup, nonNullThreadPoolKey, properties, HystrixPlugins.getInstance().getEventNotifier()); + metrics.putIfAbsent(key.name(), newCommandMetrics); + return newCommandMetrics; + } + } + } + } + + /** + * Get the {@link HystrixCommandMetrics} instance for a given {@link HystrixCommandKey} or null if one does not exist. + * + * @param key + * {@link HystrixCommandKey} of {@link HystrixCommand} instance requesting the {@link HystrixCommandMetrics} + * @return {@link HystrixCommandMetrics} + */ + public static HystrixCommandMetrics getInstance(HystrixCommandKey key) { + return metrics.get(key.name()); + } + + /** + * All registered instances of {@link HystrixCommandMetrics} + * + * @return {@code Collection} + */ + public static Collection getInstances() { + return Collections.unmodifiableCollection(metrics.values()); + } + + /** + * Clears all state from metrics. If new requests come in instances will be recreated and metrics started from scratch. + */ + /* package */ static void reset() { + for (HystrixCommandMetrics metricsInstance: getInstances()) { + metricsInstance.unsubscribeAll(); + } + metrics.clear(); + } + + private final HystrixCommandProperties properties; + private final HystrixCommandKey key; + private final HystrixCommandGroupKey group; + private final HystrixThreadPoolKey threadPoolKey; + private final AtomicInteger concurrentExecutionCount = new AtomicInteger(); + + private HealthCountsStream healthCountsStream; + private final RollingCommandEventCounterStream rollingCommandEventCounterStream; + private final CumulativeCommandEventCounterStream cumulativeCommandEventCounterStream; + private final RollingCommandLatencyDistributionStream rollingCommandLatencyDistributionStream; + private final RollingCommandUserLatencyDistributionStream rollingCommandUserLatencyDistributionStream; + private final RollingCommandMaxConcurrencyStream rollingCommandMaxConcurrencyStream; + + /* package */HystrixCommandMetrics(final HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixThreadPoolKey threadPoolKey, HystrixCommandProperties properties, HystrixEventNotifier eventNotifier) { + super(null); + this.key = key; + this.group = commandGroup; + this.threadPoolKey = threadPoolKey; + this.properties = properties; + + healthCountsStream = HealthCountsStream.getInstance(key, properties); + rollingCommandEventCounterStream = RollingCommandEventCounterStream.getInstance(key, properties); + cumulativeCommandEventCounterStream = CumulativeCommandEventCounterStream.getInstance(key, properties); + + rollingCommandLatencyDistributionStream = RollingCommandLatencyDistributionStream.getInstance(key, properties); + rollingCommandUserLatencyDistributionStream = RollingCommandUserLatencyDistributionStream.getInstance(key, properties); + rollingCommandMaxConcurrencyStream = RollingCommandMaxConcurrencyStream.getInstance(key, properties); + } + + /* package */ synchronized void resetStream() { + healthCountsStream.unsubscribe(); + HealthCountsStream.removeByKey(key); + healthCountsStream = HealthCountsStream.getInstance(key, properties); + } + + /** + * {@link HystrixCommandKey} these metrics represent. + * + * @return HystrixCommandKey + */ + public HystrixCommandKey getCommandKey() { + return key; + } + + /** + * {@link HystrixCommandGroupKey} of the {@link HystrixCommand} these metrics represent. + * + * @return HystrixCommandGroupKey + */ + public HystrixCommandGroupKey getCommandGroup() { + return group; + } + + /** + * {@link HystrixThreadPoolKey} used by {@link HystrixCommand} these metrics represent. + * + * @return HystrixThreadPoolKey + */ + public HystrixThreadPoolKey getThreadPoolKey() { + return threadPoolKey; + } + + /** + * {@link HystrixCommandProperties} of the {@link HystrixCommand} these metrics represent. + * + * @return HystrixCommandProperties + */ + public HystrixCommandProperties getProperties() { + return properties; + } + + public long getRollingCount(HystrixEventType eventType) { + return rollingCommandEventCounterStream.getLatest(eventType); + } + + public long getCumulativeCount(HystrixEventType eventType) { + return cumulativeCommandEventCounterStream.getLatest(eventType); + } + + @Override + public long getCumulativeCount(HystrixRollingNumberEvent event) { + return getCumulativeCount(HystrixEventType.from(event)); + } + + @Override + public long getRollingCount(HystrixRollingNumberEvent event) { + return getRollingCount(HystrixEventType.from(event)); + } + + /** + * Retrieve the execution time (in milliseconds) for the {@link HystrixCommand#run()} method being invoked at a given percentile. + *

+ * Percentile capture and calculation is configured via {@link HystrixCommandProperties#metricsRollingStatisticalWindowInMilliseconds()} and other related properties. + * + * @param percentile + * Percentile such as 50, 99, or 99.5. + * @return int time in milliseconds + */ + public int getExecutionTimePercentile(double percentile) { + return rollingCommandLatencyDistributionStream.getLatestPercentile(percentile); + } + + /** + * The mean (average) execution time (in milliseconds) for the {@link HystrixCommand#run()}. + *

+ * This uses the same backing data as {@link #getExecutionTimePercentile}; + * + * @return int time in milliseconds + */ + public int getExecutionTimeMean() { + return rollingCommandLatencyDistributionStream.getLatestMean(); + } + + /** + * Retrieve the total end-to-end execution time (in milliseconds) for {@link HystrixCommand#execute()} or {@link HystrixCommand#queue()} at a given percentile. + *

+ * When execution is successful this would include time from {@link #getExecutionTimePercentile} but when execution + * is being rejected, short-circuited, or timed-out then the time will differ. + *

+ * This time can be lower than {@link #getExecutionTimePercentile} when a timeout occurs and the backing + * thread that calls {@link HystrixCommand#run()} is still running. + *

+ * When rejections or short-circuits occur then {@link HystrixCommand#run()} will not be executed and thus + * not contribute time to {@link #getExecutionTimePercentile} but time will still show up in this metric for the end-to-end time. + *

+ * This metric gives visibility into the total cost of {@link HystrixCommand} execution including + * the overhead of queuing, executing and waiting for a thread to invoke {@link HystrixCommand#run()} . + *

+ * Percentile capture and calculation is configured via {@link HystrixCommandProperties#metricsRollingStatisticalWindowInMilliseconds()} and other related properties. + * + * @param percentile + * Percentile such as 50, 99, or 99.5. + * @return int time in milliseconds + */ + public int getTotalTimePercentile(double percentile) { + return rollingCommandUserLatencyDistributionStream.getLatestPercentile(percentile); + } + + /** + * The mean (average) execution time (in milliseconds) for {@link HystrixCommand#execute()} or {@link HystrixCommand#queue()}. + *

+ * This uses the same backing data as {@link #getTotalTimePercentile}; + * + * @return int time in milliseconds + */ + public int getTotalTimeMean() { + return rollingCommandUserLatencyDistributionStream.getLatestMean(); + } + + public long getRollingMaxConcurrentExecutions() { + return rollingCommandMaxConcurrencyStream.getLatestRollingMax(); + } + + /** + * Current number of concurrent executions of {@link HystrixCommand#run()}; + * + * @return int + */ + public int getCurrentConcurrentExecutionCount() { + return concurrentExecutionCount.get(); + } + + /* package-private */ void markCommandStart(HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey, HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy) { + int currentCount = concurrentExecutionCount.incrementAndGet(); + HystrixThreadEventStream.getInstance().commandExecutionStarted(commandKey, threadPoolKey, isolationStrategy, currentCount); + } + + /* package-private */ void markCommandDone(ExecutionResult executionResult, HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey, boolean executionStarted) { + HystrixThreadEventStream.getInstance().executionDone(executionResult, commandKey, threadPoolKey); + if (executionStarted) { + concurrentExecutionCount.decrementAndGet(); + } + } + + /* package-private */ HealthCountsStream getHealthCountsStream() { + return healthCountsStream; + } + + /** + * Retrieve a snapshot of total requests, error count and error percentage. + * + * This metrics should measure the actual health of a {@link HystrixCommand}. For that reason, the following are included: + *

    + *
  • {@link HystrixEventType#SUCCESS} + *
  • {@link HystrixEventType#FAILURE} + *
  • {@link HystrixEventType#TIMEOUT} + *
  • {@link HystrixEventType#THREAD_POOL_REJECTED} + *
  • {@link HystrixEventType#SEMAPHORE_REJECTED} + *

+ * The following are not included in either attempts/failures: + *

    + *
  • {@link HystrixEventType#BAD_REQUEST} - this event denotes bad arguments to the command and not a problem with the command + *
  • {@link HystrixEventType#SHORT_CIRCUITED} - this event measures a health problem in the past, not a problem with the current state + *
  • {@link HystrixEventType#CANCELLED} - this event denotes a user-cancelled command. It's not known if it would have been a success or failure, so it shouldn't count for either + *
  • All Fallback metrics + *
  • {@link HystrixEventType#EMIT} - this event is not a terminal state for the command + *
  • {@link HystrixEventType#COLLAPSED} - this event is about the batching process, not the command execution + *

+ * + * @return {@link HealthCounts} + */ + public HealthCounts getHealthCounts() { + return healthCountsStream.getLatest(); + } + + private void unsubscribeAll() { + healthCountsStream.unsubscribe(); + rollingCommandEventCounterStream.unsubscribe(); + cumulativeCommandEventCounterStream.unsubscribe(); + rollingCommandLatencyDistributionStream.unsubscribe(); + rollingCommandUserLatencyDistributionStream.unsubscribe(); + rollingCommandMaxConcurrencyStream.unsubscribe(); + } + + /** + * Number of requests during rolling window. + * Number that failed (failure + success + timeout + threadPoolRejected + semaphoreRejected). + * Error percentage; + */ + public static class HealthCounts { + private final long totalCount; + private final long errorCount; + private final int errorPercentage; + + HealthCounts(long total, long error) { + this.totalCount = total; + this.errorCount = error; + if (totalCount > 0) { + this.errorPercentage = (int) ((double) errorCount / totalCount * 100); + } else { + this.errorPercentage = 0; + } + } + + private static final HealthCounts EMPTY = new HealthCounts(0, 0); + + public long getTotalRequests() { + return totalCount; + } + + public long getErrorCount() { + return errorCount; + } + + public int getErrorPercentage() { + return errorPercentage; + } + + public HealthCounts plus(long[] eventTypeCounts) { + long updatedTotalCount = totalCount; + long updatedErrorCount = errorCount; + + long successCount = eventTypeCounts[HystrixEventType.SUCCESS.ordinal()]; + long failureCount = eventTypeCounts[HystrixEventType.FAILURE.ordinal()]; + long timeoutCount = eventTypeCounts[HystrixEventType.TIMEOUT.ordinal()]; + long threadPoolRejectedCount = eventTypeCounts[HystrixEventType.THREAD_POOL_REJECTED.ordinal()]; + long semaphoreRejectedCount = eventTypeCounts[HystrixEventType.SEMAPHORE_REJECTED.ordinal()]; + + updatedTotalCount += (successCount + failureCount + timeoutCount + threadPoolRejectedCount + semaphoreRejectedCount); + updatedErrorCount += (failureCount + timeoutCount + threadPoolRejectedCount + semaphoreRejectedCount); + return new HealthCounts(updatedTotalCount, updatedErrorCount); + } + + public static HealthCounts empty() { + return EMPTY; + } + + public String toString() { + return "HealthCounts[" + errorCount + " / " + totalCount + " : " + getErrorPercentage() + "%]"; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandProperties.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandProperties.java new file mode 100644 index 0000000..84d3733 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandProperties.java @@ -0,0 +1,791 @@ +/** + * Copyright 2012 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 static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forBoolean; +import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forInteger; +import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forString; + +import java.util.concurrent.Future; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.hystrix.strategy.properties.HystrixDynamicProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.HystrixRollingNumber; +import com.netflix.hystrix.util.HystrixRollingPercentile; + +/** + * Properties for instances of {@link HystrixCommand}. + *

+ * Default implementation of methods uses Archaius (https://github.com/Netflix/archaius) + */ +public abstract class HystrixCommandProperties { + private static final Logger logger = LoggerFactory.getLogger(HystrixCommandProperties.class); + + /* defaults */ + /* package */ static final Integer default_metricsRollingStatisticalWindow = 10000;// default => statisticalWindow: 10000 = 10 seconds (and default of 10 buckets so each bucket is 1 second) + private static final Integer default_metricsRollingStatisticalWindowBuckets = 10;// default => statisticalWindowBuckets: 10 = 10 buckets in a 10 second window so each bucket is 1 second + private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;// default => statisticalWindowVolumeThreshold: 20 requests in 10 seconds must occur before statistics matter + private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;// default => sleepWindow: 5000 = 5 seconds that we will sleep before trying again after tripping the circuit + private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;// default => errorThresholdPercentage = 50 = if 50%+ of requests in 10 seconds are failures or latent then we will trip the circuit + private static final Boolean default_circuitBreakerForceOpen = false;// default => forceCircuitOpen = false (we want to allow traffic) + /* package */ static final Boolean default_circuitBreakerForceClosed = false;// default => ignoreErrors = false + private static final Integer default_executionTimeoutInMilliseconds = 1000; // default => executionTimeoutInMilliseconds: 1000 = 1 second + private static final Boolean default_executionTimeoutEnabled = true; + private static final ExecutionIsolationStrategy default_executionIsolationStrategy = ExecutionIsolationStrategy.THREAD; + private static final Boolean default_executionIsolationThreadInterruptOnTimeout = true; + private static final Boolean default_executionIsolationThreadInterruptOnFutureCancel = false; + private static final Boolean default_metricsRollingPercentileEnabled = true; + private static final Boolean default_requestCacheEnabled = true; + private static final Integer default_fallbackIsolationSemaphoreMaxConcurrentRequests = 10; + private static final Boolean default_fallbackEnabled = true; + private static final Integer default_executionIsolationSemaphoreMaxConcurrentRequests = 10; + private static final Boolean default_requestLogEnabled = true; + private static final Boolean default_circuitBreakerEnabled = true; + private static final Integer default_metricsRollingPercentileWindow = 60000; // default to 1 minute for RollingPercentile + private static final Integer default_metricsRollingPercentileWindowBuckets = 6; // default to 6 buckets (10 seconds each in 60 second window) + private static final Integer default_metricsRollingPercentileBucketSize = 100; // default to 100 values max per bucket + private static final Integer default_metricsHealthSnapshotIntervalInMilliseconds = 500; // default to 500ms as max frequency between allowing snapshots of health (error percentage etc) + + @SuppressWarnings("unused") private final HystrixCommandKey key; + private final HystrixProperty circuitBreakerRequestVolumeThreshold; // number of requests that must be made within a statisticalWindow before open/close decisions are made using stats + private final HystrixProperty circuitBreakerSleepWindowInMilliseconds; // milliseconds after tripping circuit before allowing retry + private final HystrixProperty circuitBreakerEnabled; // Whether circuit breaker should be enabled. + private final HystrixProperty circuitBreakerErrorThresholdPercentage; // % of 'marks' that must be failed to trip the circuit + private final HystrixProperty circuitBreakerForceOpen; // a property to allow forcing the circuit open (stopping all requests) + private final HystrixProperty circuitBreakerForceClosed; // a property to allow ignoring errors and therefore never trip 'open' (ie. allow all traffic through) + private final HystrixProperty executionIsolationStrategy; // Whether a command should be executed in a separate thread or not. + private final HystrixProperty executionTimeoutInMilliseconds; // Timeout value in milliseconds for a command + private final HystrixProperty executionTimeoutEnabled; //Whether timeout should be triggered + private final HystrixProperty executionIsolationThreadPoolKeyOverride; // What thread-pool this command should run in (if running on a separate thread). + private final HystrixProperty executionIsolationSemaphoreMaxConcurrentRequests; // Number of permits for execution semaphore + private final HystrixProperty fallbackIsolationSemaphoreMaxConcurrentRequests; // Number of permits for fallback semaphore + private final HystrixProperty fallbackEnabled; // Whether fallback should be attempted. + private final HystrixProperty executionIsolationThreadInterruptOnTimeout; // Whether an underlying Future/Thread (when runInSeparateThread == true) should be interrupted after a timeout + private final HystrixProperty executionIsolationThreadInterruptOnFutureCancel; // Whether canceling an underlying Future/Thread (when runInSeparateThread == true) should interrupt the execution thread + private final HystrixProperty metricsRollingStatisticalWindowInMilliseconds; // milliseconds back that will be tracked + private final HystrixProperty metricsRollingStatisticalWindowBuckets; // number of buckets in the statisticalWindow + private final HystrixProperty metricsRollingPercentileEnabled; // Whether monitoring should be enabled (SLA and Tracers). + private final HystrixProperty metricsRollingPercentileWindowInMilliseconds; // number of milliseconds that will be tracked in RollingPercentile + private final HystrixProperty metricsRollingPercentileWindowBuckets; // number of buckets percentileWindow will be divided into + private final HystrixProperty metricsRollingPercentileBucketSize; // how many values will be stored in each percentileWindowBucket + private final HystrixProperty metricsHealthSnapshotIntervalInMilliseconds; // time between health snapshots + private final HystrixProperty requestLogEnabled; // whether command request logging is enabled. + private final HystrixProperty requestCacheEnabled; // Whether request caching is enabled. + + /** + * Isolation strategy to use when executing a {@link HystrixCommand}. + *

+ *

    + *
  • THREAD: Execute the {@link HystrixCommand#run()} method on a separate thread and restrict concurrent executions using the thread-pool size.
  • + *
  • SEMAPHORE: Execute the {@link HystrixCommand#run()} method on the calling thread and restrict concurrent executions using the semaphore permit count.
  • + *
+ */ + public static enum ExecutionIsolationStrategy { + THREAD, SEMAPHORE + } + + protected HystrixCommandProperties(HystrixCommandKey key) { + this(key, new Setter(), "hystrix"); + } + + protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder) { + this(key, builder, "hystrix"); + } + + // known that we're using deprecated HystrixPropertiesChainedServoProperty until ChainedDynamicProperty exists in Archaius + protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) { + this.key = key; + this.circuitBreakerEnabled = getProperty(propertyPrefix, key, "circuitBreaker.enabled", builder.getCircuitBreakerEnabled(), default_circuitBreakerEnabled); + this.circuitBreakerRequestVolumeThreshold = getProperty(propertyPrefix, key, "circuitBreaker.requestVolumeThreshold", builder.getCircuitBreakerRequestVolumeThreshold(), default_circuitBreakerRequestVolumeThreshold); + this.circuitBreakerSleepWindowInMilliseconds = getProperty(propertyPrefix, key, "circuitBreaker.sleepWindowInMilliseconds", builder.getCircuitBreakerSleepWindowInMilliseconds(), default_circuitBreakerSleepWindowInMilliseconds); + this.circuitBreakerErrorThresholdPercentage = getProperty(propertyPrefix, key, "circuitBreaker.errorThresholdPercentage", builder.getCircuitBreakerErrorThresholdPercentage(), default_circuitBreakerErrorThresholdPercentage); + this.circuitBreakerForceOpen = getProperty(propertyPrefix, key, "circuitBreaker.forceOpen", builder.getCircuitBreakerForceOpen(), default_circuitBreakerForceOpen); + this.circuitBreakerForceClosed = getProperty(propertyPrefix, key, "circuitBreaker.forceClosed", builder.getCircuitBreakerForceClosed(), default_circuitBreakerForceClosed); + this.executionIsolationStrategy = getProperty(propertyPrefix, key, "execution.isolation.strategy", builder.getExecutionIsolationStrategy(), default_executionIsolationStrategy); + //this property name is now misleading. //TODO figure out a good way to deprecate this property name + this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds); + this.executionTimeoutEnabled = getProperty(propertyPrefix, key, "execution.timeout.enabled", builder.getExecutionTimeoutEnabled(), default_executionTimeoutEnabled); + this.executionIsolationThreadInterruptOnTimeout = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnTimeout", builder.getExecutionIsolationThreadInterruptOnTimeout(), default_executionIsolationThreadInterruptOnTimeout); + this.executionIsolationThreadInterruptOnFutureCancel = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnFutureCancel", builder.getExecutionIsolationThreadInterruptOnFutureCancel(), default_executionIsolationThreadInterruptOnFutureCancel); + this.executionIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "execution.isolation.semaphore.maxConcurrentRequests", builder.getExecutionIsolationSemaphoreMaxConcurrentRequests(), default_executionIsolationSemaphoreMaxConcurrentRequests); + this.fallbackIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "fallback.isolation.semaphore.maxConcurrentRequests", builder.getFallbackIsolationSemaphoreMaxConcurrentRequests(), default_fallbackIsolationSemaphoreMaxConcurrentRequests); + this.fallbackEnabled = getProperty(propertyPrefix, key, "fallback.enabled", builder.getFallbackEnabled(), default_fallbackEnabled); + this.metricsRollingStatisticalWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingStats.timeInMilliseconds", builder.getMetricsRollingStatisticalWindowInMilliseconds(), default_metricsRollingStatisticalWindow); + this.metricsRollingStatisticalWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingStats.numBuckets", builder.getMetricsRollingStatisticalWindowBuckets(), default_metricsRollingStatisticalWindowBuckets); + this.metricsRollingPercentileEnabled = getProperty(propertyPrefix, key, "metrics.rollingPercentile.enabled", builder.getMetricsRollingPercentileEnabled(), default_metricsRollingPercentileEnabled); + this.metricsRollingPercentileWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingPercentile.timeInMilliseconds", builder.getMetricsRollingPercentileWindowInMilliseconds(), default_metricsRollingPercentileWindow); + this.metricsRollingPercentileWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingPercentile.numBuckets", builder.getMetricsRollingPercentileWindowBuckets(), default_metricsRollingPercentileWindowBuckets); + this.metricsRollingPercentileBucketSize = getProperty(propertyPrefix, key, "metrics.rollingPercentile.bucketSize", builder.getMetricsRollingPercentileBucketSize(), default_metricsRollingPercentileBucketSize); + this.metricsHealthSnapshotIntervalInMilliseconds = getProperty(propertyPrefix, key, "metrics.healthSnapshot.intervalInMilliseconds", builder.getMetricsHealthSnapshotIntervalInMilliseconds(), default_metricsHealthSnapshotIntervalInMilliseconds); + this.requestCacheEnabled = getProperty(propertyPrefix, key, "requestCache.enabled", builder.getRequestCacheEnabled(), default_requestCacheEnabled); + this.requestLogEnabled = getProperty(propertyPrefix, key, "requestLog.enabled", builder.getRequestLogEnabled(), default_requestLogEnabled); + + // threadpool doesn't have a global override, only instance level makes sense + this.executionIsolationThreadPoolKeyOverride = forString().add(propertyPrefix + ".command." + key.name() + ".threadPoolKeyOverride", null).build(); + } + + /** + * Whether to use a {@link HystrixCircuitBreaker} or not. If false no circuit-breaker logic will be used and all requests permitted. + *

+ * This is similar in effect to {@link #circuitBreakerForceClosed()} except that continues tracking metrics and knowing whether it + * should be open/closed, this property results in not even instantiating a circuit-breaker. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty circuitBreakerEnabled() { + return circuitBreakerEnabled; + } + + /** + * Error percentage threshold (as whole number such as 50) at which point the circuit breaker will trip open and reject requests. + *

+ * It will stay tripped for the duration defined in {@link #circuitBreakerSleepWindowInMilliseconds()}; + *

+ * The error percentage this is compared against comes from {@link HystrixCommandMetrics#getHealthCounts()}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty circuitBreakerErrorThresholdPercentage() { + return circuitBreakerErrorThresholdPercentage; + } + + /** + * If true the {@link HystrixCircuitBreaker#allowRequest()} will always return true to allow requests regardless of the error percentage from {@link HystrixCommandMetrics#getHealthCounts()}. + *

+ * The {@link #circuitBreakerForceOpen()} property takes precedence so if it set to true this property does nothing. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty circuitBreakerForceClosed() { + return circuitBreakerForceClosed; + } + + /** + * If true the {@link HystrixCircuitBreaker#allowRequest()} will always return false, causing the circuit to be open (tripped) and reject all requests. + *

+ * This property takes precedence over {@link #circuitBreakerForceClosed()}; + * + * @return {@code HystrixProperty} + */ + public HystrixProperty circuitBreakerForceOpen() { + return circuitBreakerForceOpen; + } + + /** + * Minimum number of requests in the {@link #metricsRollingStatisticalWindowInMilliseconds()} that must exist before the {@link HystrixCircuitBreaker} will trip. + *

+ * If below this number the circuit will not trip regardless of error percentage. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty circuitBreakerRequestVolumeThreshold() { + return circuitBreakerRequestVolumeThreshold; + } + + /** + * The time in milliseconds after a {@link HystrixCircuitBreaker} trips open that it should wait before trying requests again. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty circuitBreakerSleepWindowInMilliseconds() { + return circuitBreakerSleepWindowInMilliseconds; + } + + /** + * Number of concurrent requests permitted to {@link HystrixCommand#run()}. Requests beyond the concurrent limit will be rejected. + *

+ * Applicable only when {@link #executionIsolationStrategy()} == SEMAPHORE. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty executionIsolationSemaphoreMaxConcurrentRequests() { + return executionIsolationSemaphoreMaxConcurrentRequests; + } + + /** + * What isolation strategy {@link HystrixCommand#run()} will be executed with. + *

+ * If {@link ExecutionIsolationStrategy#THREAD} then it will be executed on a separate thread and concurrent requests limited by the number of threads in the thread-pool. + *

+ * If {@link ExecutionIsolationStrategy#SEMAPHORE} then it will be executed on the calling thread and concurrent requests limited by the semaphore count. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty executionIsolationStrategy() { + return executionIsolationStrategy; + } + + /** + * Whether the execution thread should attempt an interrupt (using {@link Future#cancel}) when a thread times out. + *

+ * Applicable only when {@link #executionIsolationStrategy()} == THREAD. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty executionIsolationThreadInterruptOnTimeout() { + return executionIsolationThreadInterruptOnTimeout; + } + + /** + * Whether the execution thread should be interrupted if the execution observable is unsubscribed or the future is cancelled via {@link Future#cancel(true)}). + *

+ * Applicable only when {@link #executionIsolationStrategy()} == THREAD. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty executionIsolationThreadInterruptOnFutureCancel() { + return executionIsolationThreadInterruptOnFutureCancel; + } + + /** + * Allow a dynamic override of the {@link HystrixThreadPoolKey} that will dynamically change which {@link HystrixThreadPool} a {@link HystrixCommand} executes on. + *

+ * Typically this should return NULL which will cause it to use the {@link HystrixThreadPoolKey} injected into a {@link HystrixCommand} or derived from the {@link HystrixCommandGroupKey}. + *

+ * When set the injected or derived values will be ignored and a new {@link HystrixThreadPool} created (if necessary) and the {@link HystrixCommand} will begin using the newly defined pool. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty executionIsolationThreadPoolKeyOverride() { + return executionIsolationThreadPoolKeyOverride; + } + + /** + * + * @deprecated As of release 1.4.0, replaced by {@link #executionTimeoutInMilliseconds()}. Timeout is no longer specific to thread-isolation commands, so the thread-specific name is misleading. + * + * Time in milliseconds at which point the command will timeout and halt execution. + *

+ * If {@link #executionIsolationThreadInterruptOnTimeout} == true and the command is thread-isolated, the executing thread will be interrupted. + * If the command is semaphore-isolated and a {@link HystrixObservableCommand}, that command will get unsubscribed. + *

+ * + * @return {@code HystrixProperty} + */ + @Deprecated //prefer {@link #executionTimeoutInMilliseconds} + public HystrixProperty executionIsolationThreadTimeoutInMilliseconds() { + return executionTimeoutInMilliseconds; + } + + /** + * Time in milliseconds at which point the command will timeout and halt execution. + *

+ * If {@link #executionIsolationThreadInterruptOnTimeout} == true and the command is thread-isolated, the executing thread will be interrupted. + * If the command is semaphore-isolated and a {@link HystrixObservableCommand}, that command will get unsubscribed. + *

+ * + * @return {@code HystrixProperty} + */ + public HystrixProperty executionTimeoutInMilliseconds() { + /** + * Calling a deprecated method here is a temporary workaround. We do this because {@link #executionTimeoutInMilliseconds()} is a new method (as of 1.4.0-rc.7) and an extending + * class will not have this method. It will have {@link #executionIsolationThreadTimeoutInMilliseconds()}, however. + * So, to stay compatible with an extension, we perform this redirect. + */ + return executionIsolationThreadTimeoutInMilliseconds(); + } + + /** + * Whether the timeout mechanism is enabled for this command + * + * @return {@code HystrixProperty} + * + * @since 1.4.4 + */ + public HystrixProperty executionTimeoutEnabled() { + return executionTimeoutEnabled; + } + + /** + * Number of concurrent requests permitted to {@link HystrixCommand#getFallback()}. Requests beyond the concurrent limit will fail-fast and not attempt retrieving a fallback. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty fallbackIsolationSemaphoreMaxConcurrentRequests() { + return fallbackIsolationSemaphoreMaxConcurrentRequests; + } + + /** + * Whether {@link HystrixCommand#getFallback()} should be attempted when failure occurs. + * + * @return {@code HystrixProperty} + * + * @since 1.2 + */ + public HystrixProperty fallbackEnabled() { + return fallbackEnabled; + } + + /** + * Time in milliseconds to wait between allowing health snapshots to be taken that calculate success and error percentages and affect {@link HystrixCircuitBreaker#isOpen()} status. + *

+ * On high-volume circuits the continual calculation of error percentage can become CPU intensive thus this controls how often it is calculated. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsHealthSnapshotIntervalInMilliseconds() { + return metricsHealthSnapshotIntervalInMilliseconds; + } + + /** + * Maximum number of values stored in each bucket of the rolling percentile. This is passed into {@link HystrixRollingPercentile} inside {@link HystrixCommandMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingPercentileBucketSize() { + return metricsRollingPercentileBucketSize; + } + + /** + * Whether percentile metrics should be captured using {@link HystrixRollingPercentile} inside {@link HystrixCommandMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingPercentileEnabled() { + return metricsRollingPercentileEnabled; + } + + /** + * Duration of percentile rolling window in milliseconds. This is passed into {@link HystrixRollingPercentile} inside {@link HystrixCommandMetrics}. + * + * @return {@code HystrixProperty} + * @deprecated Use {@link #metricsRollingPercentileWindowInMilliseconds()} + */ + public HystrixProperty metricsRollingPercentileWindow() { + return metricsRollingPercentileWindowInMilliseconds; + } + + /** + * Duration of percentile rolling window in milliseconds. This is passed into {@link HystrixRollingPercentile} inside {@link HystrixCommandMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingPercentileWindowInMilliseconds() { + return metricsRollingPercentileWindowInMilliseconds; + } + + /** + * Number of buckets the rolling percentile window is broken into. This is passed into {@link HystrixRollingPercentile} inside {@link HystrixCommandMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingPercentileWindowBuckets() { + return metricsRollingPercentileWindowBuckets; + } + + /** + * Duration of statistical rolling window in milliseconds. This is passed into {@link HystrixRollingNumber} inside {@link HystrixCommandMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingStatisticalWindowInMilliseconds() { + return metricsRollingStatisticalWindowInMilliseconds; + } + + /** + * Number of buckets the rolling statistical window is broken into. This is passed into {@link HystrixRollingNumber} inside {@link HystrixCommandMetrics}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingStatisticalWindowBuckets() { + return metricsRollingStatisticalWindowBuckets; + } + + /** + * Whether {@link HystrixCommand#getCacheKey()} should be used with {@link HystrixRequestCache} to provide de-duplication functionality via request-scoped caching. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty requestCacheEnabled() { + return requestCacheEnabled; + } + + /** + * Whether {@link HystrixCommand} execution and events should be logged to {@link HystrixRequestLog}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty requestLogEnabled() { + return requestLogEnabled; + } + + private static HystrixProperty getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Boolean builderOverrideValue, Boolean defaultValue) { + return forBoolean() + .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue) + .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue) + .build(); + } + + private static HystrixProperty getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Integer builderOverrideValue, Integer defaultValue) { + return forInteger() + .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue) + .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue) + .build(); + } + + @SuppressWarnings("unused") + private static HystrixProperty getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, String builderOverrideValue, String defaultValue) { + return forString() + .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue) + .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue) + .build(); + } + + private static HystrixProperty getProperty(final String propertyPrefix, final HystrixCommandKey key, final String instanceProperty, final ExecutionIsolationStrategy builderOverrideValue, final ExecutionIsolationStrategy defaultValue) { + return new ExecutionIsolationStrategyHystrixProperty(builderOverrideValue, key, propertyPrefix, defaultValue, instanceProperty); + + } + + /** + * HystrixProperty that converts a String to ExecutionIsolationStrategy so we remain TypeSafe. + */ + private static final class ExecutionIsolationStrategyHystrixProperty implements HystrixProperty { + private final HystrixDynamicProperty property; + private volatile ExecutionIsolationStrategy value; + private final ExecutionIsolationStrategy defaultValue; + + private ExecutionIsolationStrategyHystrixProperty(ExecutionIsolationStrategy builderOverrideValue, HystrixCommandKey key, String propertyPrefix, ExecutionIsolationStrategy defaultValue, String instanceProperty) { + this.defaultValue = defaultValue; + String overrideValue = null; + if (builderOverrideValue != null) { + overrideValue = builderOverrideValue.name(); + } + property = forString() + .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, overrideValue) + .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue.name()) + .build(); + + // initialize the enum value from the property + parseProperty(); + + // use a callback to handle changes so we only handle the parse cost on updates rather than every fetch + property.addCallback(new Runnable() { + + @Override + public void run() { + // when the property value changes we'll update the value + parseProperty(); + } + + }); + } + + @Override + public ExecutionIsolationStrategy get() { + return value; + } + + private void parseProperty() { + try { + value = ExecutionIsolationStrategy.valueOf(property.get()); + } catch (Exception e) { + logger.error("Unable to derive ExecutionIsolationStrategy from property value: " + property.get(), e); + // use the default value + value = defaultValue; + } + } + } + + /** + * Factory method to retrieve the default Setter. + */ + public static Setter Setter() { + return new Setter(); + } + + /** + * Factory method to retrieve the default Setter. + * Groovy has a bug (GROOVY-6286) which does not allow method names and inner classes to have the same name + * This method fixes Issue #967 and allows Groovy consumers to choose this method and not trigger the bug + */ + public static Setter defaultSetter() { + return Setter(); + } + + /** + * Fluent interface that allows chained setting of properties that can be passed into a {@link HystrixCommand} constructor to inject instance specific property overrides. + *

+ * See {@link HystrixPropertiesStrategy} for more information on order of precedence. + *

+ * Example: + *

+ *

 {@code
+     * HystrixCommandProperties.Setter()
+     *           .withExecutionTimeoutInMilliseconds(100)
+     *           .withExecuteCommandOnSeparateThread(true);
+     * } 
+ * + * @NotThreadSafe + */ + public static class Setter { + + private Boolean circuitBreakerEnabled = null; + private Integer circuitBreakerErrorThresholdPercentage = null; + private Boolean circuitBreakerForceClosed = null; + private Boolean circuitBreakerForceOpen = null; + private Integer circuitBreakerRequestVolumeThreshold = null; + private Integer circuitBreakerSleepWindowInMilliseconds = null; + private Integer executionIsolationSemaphoreMaxConcurrentRequests = null; + private ExecutionIsolationStrategy executionIsolationStrategy = null; + private Boolean executionIsolationThreadInterruptOnTimeout = null; + private Boolean executionIsolationThreadInterruptOnFutureCancel = null; + private Integer executionTimeoutInMilliseconds = null; + private Boolean executionTimeoutEnabled = null; + private Integer fallbackIsolationSemaphoreMaxConcurrentRequests = null; + private Boolean fallbackEnabled = null; + private Integer metricsHealthSnapshotIntervalInMilliseconds = null; + private Integer metricsRollingPercentileBucketSize = null; + private Boolean metricsRollingPercentileEnabled = null; + private Integer metricsRollingPercentileWindowInMilliseconds = null; + private Integer metricsRollingPercentileWindowBuckets = null; + /* null means it hasn't been overridden */ + private Integer metricsRollingStatisticalWindowInMilliseconds = null; + private Integer metricsRollingStatisticalWindowBuckets = null; + private Boolean requestCacheEnabled = null; + private Boolean requestLogEnabled = null; + + /* package */ Setter() { + } + + public Boolean getCircuitBreakerEnabled() { + return circuitBreakerEnabled; + } + + public Integer getCircuitBreakerErrorThresholdPercentage() { + return circuitBreakerErrorThresholdPercentage; + } + + public Boolean getCircuitBreakerForceClosed() { + return circuitBreakerForceClosed; + } + + public Boolean getCircuitBreakerForceOpen() { + return circuitBreakerForceOpen; + } + + public Integer getCircuitBreakerRequestVolumeThreshold() { + return circuitBreakerRequestVolumeThreshold; + } + + public Integer getCircuitBreakerSleepWindowInMilliseconds() { + return circuitBreakerSleepWindowInMilliseconds; + } + + public Integer getExecutionIsolationSemaphoreMaxConcurrentRequests() { + return executionIsolationSemaphoreMaxConcurrentRequests; + } + + public ExecutionIsolationStrategy getExecutionIsolationStrategy() { + return executionIsolationStrategy; + } + + public Boolean getExecutionIsolationThreadInterruptOnTimeout() { + return executionIsolationThreadInterruptOnTimeout; + } + + public Boolean getExecutionIsolationThreadInterruptOnFutureCancel() { + return executionIsolationThreadInterruptOnFutureCancel; + } + + /** + * @deprecated As of 1.4.0, use {@link #getExecutionTimeoutInMilliseconds()} + */ + @Deprecated + public Integer getExecutionIsolationThreadTimeoutInMilliseconds() { + return executionTimeoutInMilliseconds; + } + + public Integer getExecutionTimeoutInMilliseconds() { + return executionTimeoutInMilliseconds; + } + + public Boolean getExecutionTimeoutEnabled() { + return executionTimeoutEnabled; + } + + public Integer getFallbackIsolationSemaphoreMaxConcurrentRequests() { + return fallbackIsolationSemaphoreMaxConcurrentRequests; + } + + public Boolean getFallbackEnabled() { + return fallbackEnabled; + } + + public Integer getMetricsHealthSnapshotIntervalInMilliseconds() { + return metricsHealthSnapshotIntervalInMilliseconds; + } + + public Integer getMetricsRollingPercentileBucketSize() { + return metricsRollingPercentileBucketSize; + } + + public Boolean getMetricsRollingPercentileEnabled() { + return metricsRollingPercentileEnabled; + } + + public Integer getMetricsRollingPercentileWindowInMilliseconds() { + return metricsRollingPercentileWindowInMilliseconds; + } + + public Integer getMetricsRollingPercentileWindowBuckets() { + return metricsRollingPercentileWindowBuckets; + } + + public Integer getMetricsRollingStatisticalWindowInMilliseconds() { + return metricsRollingStatisticalWindowInMilliseconds; + } + + public Integer getMetricsRollingStatisticalWindowBuckets() { + return metricsRollingStatisticalWindowBuckets; + } + + public Boolean getRequestCacheEnabled() { + return requestCacheEnabled; + } + + public Boolean getRequestLogEnabled() { + return requestLogEnabled; + } + + public Setter withCircuitBreakerEnabled(boolean value) { + this.circuitBreakerEnabled = value; + return this; + } + + public Setter withCircuitBreakerErrorThresholdPercentage(int value) { + this.circuitBreakerErrorThresholdPercentage = value; + return this; + } + + public Setter withCircuitBreakerForceClosed(boolean value) { + this.circuitBreakerForceClosed = value; + return this; + } + + public Setter withCircuitBreakerForceOpen(boolean value) { + this.circuitBreakerForceOpen = value; + return this; + } + + public Setter withCircuitBreakerRequestVolumeThreshold(int value) { + this.circuitBreakerRequestVolumeThreshold = value; + return this; + } + + public Setter withCircuitBreakerSleepWindowInMilliseconds(int value) { + this.circuitBreakerSleepWindowInMilliseconds = value; + return this; + } + + public Setter withExecutionIsolationSemaphoreMaxConcurrentRequests(int value) { + this.executionIsolationSemaphoreMaxConcurrentRequests = value; + return this; + } + + public Setter withExecutionIsolationStrategy(ExecutionIsolationStrategy value) { + this.executionIsolationStrategy = value; + return this; + } + + public Setter withExecutionIsolationThreadInterruptOnTimeout(boolean value) { + this.executionIsolationThreadInterruptOnTimeout = value; + return this; + } + + public Setter withExecutionIsolationThreadInterruptOnFutureCancel(boolean value) { + this.executionIsolationThreadInterruptOnFutureCancel = value; + return this; + } + + /** + * @deprecated As of 1.4.0, replaced with {@link #withExecutionTimeoutInMilliseconds(int)}. Timeouts are no longer applied only to thread-isolated commands, so a thread-specific name is misleading + */ + @Deprecated + public Setter withExecutionIsolationThreadTimeoutInMilliseconds(int value) { + this.executionTimeoutInMilliseconds = value; + return this; + } + + public Setter withExecutionTimeoutInMilliseconds(int value) { + this.executionTimeoutInMilliseconds = value; + return this; + } + + public Setter withExecutionTimeoutEnabled(boolean value) { + this.executionTimeoutEnabled = value; + return this; + } + + public Setter withFallbackIsolationSemaphoreMaxConcurrentRequests(int value) { + this.fallbackIsolationSemaphoreMaxConcurrentRequests = value; + return this; + } + + public Setter withFallbackEnabled(boolean value) { + this.fallbackEnabled = value; + return this; + } + + public Setter withMetricsHealthSnapshotIntervalInMilliseconds(int value) { + this.metricsHealthSnapshotIntervalInMilliseconds = value; + return this; + } + + public Setter withMetricsRollingPercentileBucketSize(int value) { + this.metricsRollingPercentileBucketSize = value; + return this; + } + + public Setter withMetricsRollingPercentileEnabled(boolean value) { + this.metricsRollingPercentileEnabled = value; + return this; + } + + public Setter withMetricsRollingPercentileWindowInMilliseconds(int value) { + this.metricsRollingPercentileWindowInMilliseconds = value; + return this; + } + + public Setter withMetricsRollingPercentileWindowBuckets(int value) { + this.metricsRollingPercentileWindowBuckets = value; + return this; + } + + public Setter withMetricsRollingStatisticalWindowInMilliseconds(int value) { + this.metricsRollingStatisticalWindowInMilliseconds = value; + return this; + } + + public Setter withMetricsRollingStatisticalWindowBuckets(int value) { + this.metricsRollingStatisticalWindowBuckets = value; + return this; + } + + public Setter withRequestCacheEnabled(boolean value) { + this.requestCacheEnabled = value; + return this; + } + + public Setter withRequestLogEnabled(boolean value) { + this.requestLogEnabled = value; + return this; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandResponseFromCache.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandResponseFromCache.java new file mode 100644 index 0000000..6558d1d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandResponseFromCache.java @@ -0,0 +1,55 @@ +package com.netflix.hystrix; + +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Action1; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class HystrixCommandResponseFromCache extends HystrixCachedObservable { + private final AbstractCommand originalCommand; + + /* package-private */ HystrixCommandResponseFromCache(Observable originalObservable, final AbstractCommand originalCommand) { + super(originalObservable); + this.originalCommand = originalCommand; + } + + public Observable toObservableWithStateCopiedInto(final AbstractCommand commandToCopyStateInto) { + final AtomicBoolean completionLogicRun = new AtomicBoolean(false); + + return cachedObservable + .doOnError(new Action1() { + @Override + public void call(Throwable throwable) { + if (completionLogicRun.compareAndSet(false, true)) { + commandCompleted(commandToCopyStateInto); + } + } + }) + .doOnCompleted(new Action0() { + @Override + public void call() { + if (completionLogicRun.compareAndSet(false, true)) { + commandCompleted(commandToCopyStateInto); + } + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + if (completionLogicRun.compareAndSet(false, true)) { + commandUnsubscribed(commandToCopyStateInto); + } + } + }); + } + + private void commandCompleted(final AbstractCommand commandToCopyStateInto) { + commandToCopyStateInto.executionResult = originalCommand.executionResult; + } + + private void commandUnsubscribed(final AbstractCommand commandToCopyStateInto) { + commandToCopyStateInto.executionResult = commandToCopyStateInto.executionResult.addEvent(HystrixEventType.CANCELLED); + commandToCopyStateInto.executionResult = commandToCopyStateInto.executionResult.setExecutionLatency(-1); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCounters.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCounters.java new file mode 100644 index 0000000..2b42c2c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCounters.java @@ -0,0 +1,66 @@ +/** + * 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 java.util.concurrent.atomic.AtomicInteger; + +/** + * Class with global statistics on Hystrix runtime behavior. + * All of the data available via this class is static and scoped at the JVM level + */ +public class HystrixCounters { + private static final AtomicInteger concurrentThreadsExecuting = new AtomicInteger(0); + + /* package-private */ static int incrementGlobalConcurrentThreads() { + return concurrentThreadsExecuting.incrementAndGet(); + } + + /* package-private */ static int decrementGlobalConcurrentThreads() { + return concurrentThreadsExecuting.decrementAndGet(); + } + + /** + * Return the number of currently-executing Hystrix threads + * @return number of currently-executing Hystrix threads + */ + public static int getGlobalConcurrentThreadsExecuting() { + return concurrentThreadsExecuting.get(); + } + + /** + * Return the number of unique {@link HystrixCommand}s that have been registered + * @return number of unique {@link HystrixCommand}s that have been registered + */ + public static int getCommandCount() { + return HystrixCommandKey.Factory.getCommandCount(); + } + + /** + * Return the number of unique {@link HystrixThreadPool}s that have been registered + * @return number of unique {@link HystrixThreadPool}s that have been registered + */ + public static int getThreadPoolCount() { + return HystrixThreadPoolKey.Factory.getThreadPoolCount(); + } + + /** + * Return the number of unique {@link HystrixCommandGroupKey}s that have been registered + * @return number of unique {@link HystrixCommandGroupKey}s that have been registered + */ + public static int getGroupCount() { + return HystrixCommandGroupKey.Factory.getGroupCount(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixEventType.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixEventType.java new file mode 100644 index 0000000..bcc0575 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixEventType.java @@ -0,0 +1,145 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.util.HystrixRollingNumberEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * Various states/events that execution can result in or have tracked. + *

+ * These are most often accessed via {@link HystrixRequestLog} or {@link HystrixCommand#getExecutionEvents()}. + */ +public enum HystrixEventType { + EMIT(false), + SUCCESS(true), + FAILURE(false), + TIMEOUT(false), + BAD_REQUEST(true), + SHORT_CIRCUITED(false), + THREAD_POOL_REJECTED(false), + SEMAPHORE_REJECTED(false), + FALLBACK_EMIT(false), + FALLBACK_SUCCESS(true), + FALLBACK_FAILURE(true), + FALLBACK_REJECTION(true), + FALLBACK_DISABLED(true), + FALLBACK_MISSING(true), + EXCEPTION_THROWN(false), + RESPONSE_FROM_CACHE(true), + CANCELLED(true), + COLLAPSED(false), + COMMAND_MAX_ACTIVE(false); + + private final boolean isTerminal; + + HystrixEventType(boolean isTerminal) { + this.isTerminal = isTerminal; + } + + public boolean isTerminal() { + return isTerminal; + } + + public static HystrixEventType from(HystrixRollingNumberEvent event) { + switch (event) { + case EMIT: return EMIT; + case SUCCESS: return SUCCESS; + case FAILURE: return FAILURE; + case TIMEOUT: return TIMEOUT; + case SHORT_CIRCUITED: return SHORT_CIRCUITED; + case THREAD_POOL_REJECTED: return THREAD_POOL_REJECTED; + case SEMAPHORE_REJECTED: return SEMAPHORE_REJECTED; + case FALLBACK_EMIT: return FALLBACK_EMIT; + case FALLBACK_SUCCESS: return FALLBACK_SUCCESS; + case FALLBACK_FAILURE: return FALLBACK_FAILURE; + case FALLBACK_REJECTION: return FALLBACK_REJECTION; + case FALLBACK_DISABLED: return FALLBACK_DISABLED; + case FALLBACK_MISSING: return FALLBACK_MISSING; + case EXCEPTION_THROWN: return EXCEPTION_THROWN; + case RESPONSE_FROM_CACHE: return RESPONSE_FROM_CACHE; + case COLLAPSED: return COLLAPSED; + case BAD_REQUEST: return BAD_REQUEST; + case COMMAND_MAX_ACTIVE: return COMMAND_MAX_ACTIVE; + default: + throw new RuntimeException("Not an event that can be converted to HystrixEventType : " + event); + } + } + + /** + * List of events that throw an Exception to the caller + */ + public final static List EXCEPTION_PRODUCING_EVENT_TYPES = new ArrayList(); + + /** + * List of events that are terminal + */ + public final static List TERMINAL_EVENT_TYPES = new ArrayList(); + + static { + EXCEPTION_PRODUCING_EVENT_TYPES.add(BAD_REQUEST); + EXCEPTION_PRODUCING_EVENT_TYPES.add(FALLBACK_FAILURE); + EXCEPTION_PRODUCING_EVENT_TYPES.add(FALLBACK_DISABLED); + EXCEPTION_PRODUCING_EVENT_TYPES.add(FALLBACK_MISSING); + EXCEPTION_PRODUCING_EVENT_TYPES.add(FALLBACK_REJECTION); + + for (HystrixEventType eventType: HystrixEventType.values()) { + if (eventType.isTerminal()) { + TERMINAL_EVENT_TYPES.add(eventType); + } + } + } + + public enum ThreadPool { + EXECUTED, REJECTED; + + public static ThreadPool from(HystrixRollingNumberEvent event) { + switch (event) { + case THREAD_EXECUTION: return EXECUTED; + case THREAD_POOL_REJECTED: return REJECTED; + default: + throw new RuntimeException("Not an event that can be converted to HystrixEventType.ThreadPool : " + event); + } + } + + public static ThreadPool from(HystrixEventType eventType) { + switch (eventType) { + case SUCCESS: return EXECUTED; + case FAILURE: return EXECUTED; + case TIMEOUT: return EXECUTED; + case BAD_REQUEST: return EXECUTED; + case THREAD_POOL_REJECTED: return REJECTED; + default: return null; + } + } + } + + public enum Collapser { + BATCH_EXECUTED, ADDED_TO_BATCH, RESPONSE_FROM_CACHE; + + public static Collapser from(HystrixRollingNumberEvent event) { + switch (event) { + case COLLAPSER_BATCH: return BATCH_EXECUTED; + case COLLAPSER_REQUEST_BATCHED: return ADDED_TO_BATCH; + case RESPONSE_FROM_CACHE: return RESPONSE_FROM_CACHE; + default: + throw new RuntimeException("Not an event that can be converted to HystrixEventType.Collapser : " + event); + } + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutable.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutable.java new file mode 100644 index 0000000..6312186 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutable.java @@ -0,0 +1,93 @@ +/** + * Copyright 2012 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 java.util.concurrent.Future; + +import rx.Observable; +import rx.schedulers.Schedulers; + +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; + +/** + * Common interface for executables ({@link HystrixCommand} and {@link HystrixCollapser}) so client code can treat them the same and combine in typed collections if desired. + * + * @param + */ +public interface HystrixExecutable extends HystrixInvokable { + + /** + * Used for synchronous execution of command. + * + * @return R + * Result of {@link HystrixCommand} execution + * @throws HystrixRuntimeException + * if an error occurs and a fallback cannot be retrieved + * @throws HystrixBadRequestException + * if the {@link HystrixCommand} instance considers request arguments to be invalid and needs to throw an error that does not represent a system failure + */ + public R execute(); + + /** + * Used for asynchronous execution of command. + *

+ * This will queue up the command on the thread pool and return an {@link Future} to get the result once it completes. + *

+ * NOTE: If configured to not run in a separate thread, this will have the same effect as {@link #execute()} and will block. + *

+ * We don't throw an exception in that case but just flip to synchronous execution so code doesn't need to change in order to switch a circuit from running a separate thread to the calling thread. + * + * @return {@code Future} Result of {@link HystrixCommand} execution + * @throws HystrixRuntimeException + * if an error occurs and a fallback cannot be retrieved + * @throws HystrixBadRequestException + * if the {@link HystrixCommand} instance considers request arguments to be invalid and needs to throw an error that does not represent a system failure + */ + public Future queue(); + + /** + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. + *

+ * This eagerly starts execution of the command the same as {@link #queue()} and {@link #execute()}. + * A lazy {@link Observable} can be obtained from {@link HystrixCommand#toObservable()} or {@link HystrixCollapser#toObservable()}. + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#computation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ *

+ * See https://github.com/Netflix/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of the command execution or a fallback if the command fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public Observable observe(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokable.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokable.java new file mode 100644 index 0000000..f5b200b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokable.java @@ -0,0 +1,23 @@ +/** + * 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; + +/** + * Marker interface for Hystrix commands that can be invoked. + */ +public interface HystrixInvokable { + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokableInfo.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokableInfo.java new file mode 100644 index 0000000..b2c8b96 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokableInfo.java @@ -0,0 +1,75 @@ +/** + * Copyright 2014 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 java.util.List; + +public interface HystrixInvokableInfo { + + HystrixCommandGroupKey getCommandGroup(); + + HystrixCommandKey getCommandKey(); + + HystrixThreadPoolKey getThreadPoolKey(); + + String getPublicCacheKey(); //have to use public in the name, as there's already a protected {@link AbstractCommand#getCacheKey()} method. + + HystrixCollapserKey getOriginatingCollapserKey(); + + HystrixCommandMetrics getMetrics(); + + HystrixCommandProperties getProperties(); + + boolean isCircuitBreakerOpen(); + + boolean isExecutionComplete(); + + boolean isExecutedInThread(); + + boolean isSuccessfulExecution(); + + boolean isFailedExecution(); + + Throwable getFailedExecutionException(); + + boolean isResponseFromFallback(); + + boolean isResponseTimedOut(); + + boolean isResponseShortCircuited(); + + boolean isResponseFromCache(); + + boolean isResponseRejected(); + + boolean isResponseSemaphoreRejected(); + + boolean isResponseThreadPoolRejected(); + + List getExecutionEvents(); + + int getNumberEmissions(); + + int getNumberFallbackEmissions(); + + int getNumberCollapsed(); + + int getExecutionTimeInMilliseconds(); + + long getCommandRunStartTimeInNanos(); + + ExecutionResult.EventCounts getEventCounts(); +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixKey.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixKey.java new file mode 100644 index 0000000..905cb92 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixKey.java @@ -0,0 +1,34 @@ +package com.netflix.hystrix; + +/** + * Basic class for hystrix keys + */ +public interface HystrixKey { + /** + * The word 'name' is used instead of 'key' so that Enums can implement this interface and it work natively. + * + * @return String + */ + String name(); + + /** + * Default implementation of the interface + */ + abstract class HystrixKeyDefault implements HystrixKey { + private final String name; + + public HystrixKeyDefault(String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixMetrics.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixMetrics.java new file mode 100644 index 0000000..5850168 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixMetrics.java @@ -0,0 +1,55 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.util.HystrixRollingNumber; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; + +/** + * Abstract base class for Hystrix metrics + */ +public abstract class HystrixMetrics { + + protected final HystrixRollingNumber counter; + + protected HystrixMetrics(HystrixRollingNumber counter) { + this.counter = counter; + } + /** + * Get the cumulative count since the start of the application for the given {@link HystrixRollingNumberEvent}. + * + * @param event + * {@link HystrixRollingNumberEvent} of the event to retrieve a sum for + * @return long cumulative count + */ + public long getCumulativeCount(HystrixRollingNumberEvent event) { + return counter.getCumulativeSum(event); + } + + /** + * Get the rolling count for the given {@link HystrixRollingNumberEvent}. + *

+ * The rolling window is defined by {@link HystrixCommandProperties#metricsRollingStatisticalWindowInMilliseconds()}. + * + * @param event + * {@link HystrixRollingNumberEvent} of the event to retrieve a sum for + * @return long rolling count + */ + public long getRollingCount(HystrixRollingNumberEvent event) { + return counter.getRollingSum(event); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservable.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservable.java new file mode 100644 index 0000000..3ecef10 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservable.java @@ -0,0 +1,94 @@ +/** + * Copyright 2014 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 rx.Observable; +import rx.schedulers.Schedulers; + +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; + +/** + * Common interface for executables that implement the Observable methods {@link #observe()} and {@link #toObservable()} so client code can treat them the same and combine in typed collections if desired. + * + * @param + */ +public interface HystrixObservable extends HystrixInvokable { + + /** + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. + *

+ * This eagerly starts execution of the command the same as {@link HystrixCommand#queue()} and {@link HystrixCommand#execute()}. + *

+ * A lazy {@link Observable} can be obtained from {@link #toObservable()}. + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#computation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ *

+ * See https://github.com/ReactiveX/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of the command execution or a fallback if the command execution fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public Observable observe(); + + /** + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. + *

+ * This lazily starts execution of the command only once the {@link Observable} is subscribed to. + *

+ * An eager {@link Observable} can be obtained from {@link #observe()} + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#computation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ *

+ * See https://github.com/ReactiveX/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of the command execution or a fallback if the command execution fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public Observable toObservable(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCollapser.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCollapser.java new file mode 100644 index 0000000..a50e7cc --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCollapser.java @@ -0,0 +1,590 @@ +/** + * Copyright 2014 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 com.netflix.hystrix.HystrixCollapser.CollapsedRequest; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.collapser.CollapserTimer; +import com.netflix.hystrix.collapser.HystrixCollapserBridge; +import com.netflix.hystrix.collapser.RealCollapserTimer; +import com.netflix.hystrix.collapser.RequestCollapser; +import com.netflix.hystrix.collapser.RequestCollapserFactory; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherFactory; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.Scheduler; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.schedulers.Schedulers; +import rx.subjects.ReplaySubject; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Collapse multiple requests into a single {@link HystrixCommand} execution based on a time window and optionally a max batch size. + *

+ * This allows an object model to have multiple calls to the command that execute/queue many times in a short period (milliseconds) and have them all get batched into a single backend call. + *

+ * Typically the time window is something like 10ms give or take. + *

+ * NOTE: Do NOT retain any state within instances of this class. + *

+ * It must be stateless or else it will be non-deterministic because most instances are discarded while some are retained and become the + * "collapsers" for all the ones that are discarded. + * + * @param + * The key used to match BatchReturnType and RequestArgumentType + * @param + * The type returned from the {@link HystrixCommand} that will be invoked on batch executions. + * @param + * The type returned from this command. + * @param + * The type of the request argument. If multiple arguments are needed, wrap them in another object or a Tuple. + */ +public abstract class HystrixObservableCollapser implements HystrixObservable { + + static final Logger logger = LoggerFactory.getLogger(HystrixObservableCollapser.class); + + private final RequestCollapserFactory collapserFactory; + private final HystrixRequestCache requestCache; + private final HystrixCollapserBridge collapserInstanceWrapper; + private final HystrixCollapserMetrics metrics; + + /** + * The scope of request collapsing. + *

    + *
  • REQUEST: Requests within the scope of a {@link HystrixRequestContext} will be collapsed. + *

    + * Typically this means that requests within a single user-request (ie. HTTP request) are collapsed. No interaction with other user requests. 1 queue per user request. + *

  • + *
  • GLOBAL: Requests from any thread (ie. all HTTP requests) within the JVM will be collapsed. 1 queue for entire app.
  • + *
+ */ + public static enum Scope implements RequestCollapserFactory.Scope { + REQUEST, GLOBAL + } + + /** + * Collapser with default {@link HystrixCollapserKey} derived from the implementing class name and scoped to {@link Scope#REQUEST} and default configuration. + */ + protected HystrixObservableCollapser() { + this(Setter.withCollapserKey(null).andScope(Scope.REQUEST)); + } + + /** + * Collapser scoped to {@link Scope#REQUEST} and default configuration. + * + * @param collapserKey + * {@link HystrixCollapserKey} that identifies this collapser and provides the key used for retrieving properties, request caches, publishing metrics etc. + */ + protected HystrixObservableCollapser(HystrixCollapserKey collapserKey) { + this(Setter.withCollapserKey(collapserKey).andScope(Scope.REQUEST)); + } + + /** + * Construct a {@link HystrixObservableCollapser} with defined {@link Setter} that allows + * injecting property and strategy overrides and other optional arguments. + *

+ * Null values will result in the default being used. + * + * @param setter + * Fluent interface for constructor arguments + */ + protected HystrixObservableCollapser(Setter setter) { + this(setter.collapserKey, setter.scope, new RealCollapserTimer(), setter.propertiesSetter, null); + } + + /* package for tests */HystrixObservableCollapser(HystrixCollapserKey collapserKey, Scope scope, CollapserTimer timer, HystrixCollapserProperties.Setter propertiesBuilder, HystrixCollapserMetrics metrics) { + if (collapserKey == null || collapserKey.name().trim().equals("")) { + String defaultKeyName = getDefaultNameFromClass(getClass()); + collapserKey = HystrixCollapserKey.Factory.asKey(defaultKeyName); + } + + HystrixCollapserProperties properties = HystrixPropertiesFactory.getCollapserProperties(collapserKey, propertiesBuilder); + this.collapserFactory = new RequestCollapserFactory(collapserKey, scope, timer, properties); + this.requestCache = HystrixRequestCache.getInstance(collapserKey, HystrixPlugins.getInstance().getConcurrencyStrategy()); + + if (metrics == null) { + this.metrics = HystrixCollapserMetrics.getInstance(collapserKey, properties); + } else { + this.metrics = metrics; + } + + final HystrixObservableCollapser self = this; + + /* strategy: HystrixMetricsPublisherCollapser */ + HystrixMetricsPublisherFactory.createOrRetrievePublisherForCollapser(collapserKey, this.metrics, properties); + + + /** + * Used to pass public method invocation to the underlying implementation in a separate package while leaving the methods 'protected' in this class. + */ + collapserInstanceWrapper = new HystrixCollapserBridge() { + + @Override + public Collection>> shardRequests(Collection> requests) { + Collection>> shards = self.shardRequests(requests); + self.metrics.markShards(shards.size()); + return shards; + } + + @Override + public Observable createObservableCommand(Collection> requests) { + HystrixObservableCommand command = self.createCommand(requests); + + // mark the number of requests being collapsed together + command.markAsCollapsedCommand(this.getCollapserKey(), requests.size()); + self.metrics.markBatch(requests.size()); + return command.toObservable(); + } + + @Override + public Observable mapResponseToRequests(Observable batchResponse, Collection> requests) { + Func1 requestKeySelector = self.getRequestArgumentKeySelector(); + final Func1 batchResponseKeySelector = self.getBatchReturnTypeKeySelector(); + final Func1 mapBatchTypeToResponseType = self.getBatchReturnTypeToResponseTypeMapper(); + + // index the requests by key + final Map> requestsByKey = new HashMap>(requests.size()); + for (CollapsedRequest cr : requests) { + K requestArg = requestKeySelector.call(cr.getArgument()); + requestsByKey.put(requestArg, cr); + } + final Set seenKeys = new HashSet(); + + // observe the responses and join with the requests by key + return batchResponse + .doOnNext(new Action1() { + @Override + public void call(BatchReturnType batchReturnType) { + try { + K responseKey = batchResponseKeySelector.call(batchReturnType); + CollapsedRequest requestForResponse = requestsByKey.get(responseKey); + if (requestForResponse != null) { + requestForResponse.emitResponse(mapBatchTypeToResponseType.call(batchReturnType)); + // now add this to seenKeys, so we can later check what was seen, and what was unseen + seenKeys.add(responseKey); + } else { + logger.warn("Batch Response contained a response key not in request batch : {}", responseKey); + } + } catch (Throwable ex) { + logger.warn("Uncaught error during demultiplexing of BatchResponse", ex); + } + } + }) + .doOnError(new Action1() { + @Override + public void call(Throwable t) { + Exception ex = getExceptionFromThrowable(t); + for (CollapsedRequest collapsedReq : requestsByKey.values()) { + collapsedReq.setException(ex); + } + } + }) + .doOnCompleted(new Action0() { + @Override + public void call() { + + for (Map.Entry> entry : requestsByKey.entrySet()) { + K key = entry.getKey(); + CollapsedRequest collapsedReq = entry.getValue(); + if (!seenKeys.contains(key)) { + try { + onMissingResponse(collapsedReq); + } catch (Throwable ex) { + collapsedReq.setException(new RuntimeException("Error in HystrixObservableCollapser.onMissingResponse handler", ex)); + } + } + //then unconditionally issue an onCompleted. this ensures the downstream gets a terminal, regardless of how onMissingResponse was implemented + collapsedReq.setComplete(); + } + } + }).ignoreElements().cast(Void.class); + } + + @Override + public HystrixCollapserKey getCollapserKey() { + return self.getCollapserKey(); + } + + }; + } + + protected Exception getExceptionFromThrowable(Throwable t) { + Exception e; + if (t instanceof Exception) { + e = (Exception) t; + } else { + // Hystrix 1.x uses Exception, not Throwable so to prevent a breaking change Throwable will be wrapped in Exception + e = new Exception("Throwable caught while executing.", t); + } + return e; + } + + + private HystrixCollapserProperties getProperties() { + return collapserFactory.getProperties(); + } + + /** + * Key of the {@link HystrixObservableCollapser} used for properties, metrics, caches, reporting etc. + * + * @return {@link HystrixCollapserKey} identifying this {@link HystrixObservableCollapser} instance + */ + public HystrixCollapserKey getCollapserKey() { + return collapserFactory.getCollapserKey(); + } + + /** + * Scope of collapsing. + *

+ *

    + *
  • REQUEST: Requests within the scope of a {@link HystrixRequestContext} will be collapsed. + *

    + * Typically this means that requests within a single user-request (ie. HTTP request) are collapsed. No interaction with other user requests. 1 queue per user request. + *

  • + *
  • GLOBAL: Requests from any thread (ie. all HTTP requests) within the JVM will be collapsed. 1 queue for entire app.
  • + *
+ *

+ * Default: {@link Scope#REQUEST} (defined via constructor) + * + * @return {@link Scope} that collapsing should be performed within. + */ + public Scope getScope() { + return Scope.valueOf(collapserFactory.getScope().name()); + } + + /** + * Return the {@link HystrixCollapserMetrics} for this collapser + * @return {@link HystrixCollapserMetrics} for this collapser + */ + public HystrixCollapserMetrics getMetrics() { + return metrics; + } + + /** + * The request arguments to be passed to the {@link HystrixCommand}. + *

+ * Typically this means to take the argument(s) provided to the constructor and return it here. + *

+ * If there are multiple arguments that need to be bundled, create a single object to contain them, or use a Tuple. + * + * @return RequestArgumentType + */ + public abstract RequestArgumentType getRequestArgument(); + + /** + * Factory method to create a new {@link HystrixObservableCommand}{@code } command object each time a batch needs to be executed. + *

+ * Do not return the same instance each time. Return a new instance on each invocation. + *

+ * Process the 'requests' argument into the arguments the command object needs to perform its work. + *

+ * If a batch or requests needs to be split (sharded) into multiple commands, see {@link #shardRequests}

+ * IMPLEMENTATION NOTE: Be fast (ie. <1ms) in this method otherwise it can block the Timer from executing subsequent batches. Do not do any processing beyond constructing the command and returning + * it. + * + * @param requests + * {@code Collection>} containing {@link CollapsedRequest} objects containing the arguments of each request collapsed in this batch. + * @return {@link HystrixObservableCommand}{@code } which when executed will retrieve results for the batch of arguments as found in the Collection of {@link CollapsedRequest} + * objects + */ + protected abstract HystrixObservableCommand createCommand(Collection> requests); + + /** + * Override to split (shard) a batch of requests into multiple batches that will each call createCommand separately. + *

+ * The purpose of this is to allow collapsing to work for services that have sharded backends and batch executions that need to be shard-aware. + *

+ * For example, a batch of 100 requests could be split into 4 different batches sharded on name (ie. a-g, h-n, o-t, u-z) that each result in a separate {@link HystrixCommand} being created and + * executed for them. + *

+ * By default this method does nothing to the Collection and is a pass-thru. + * + * @param requests + * {@code Collection>} containing {@link CollapsedRequest} objects containing the arguments of each request collapsed in this batch. + * @return Collection of {@code Collection>} objects sharded according to business rules. + *

The CollapsedRequest instances should not be modified or wrapped as the CollapsedRequest instance object contains state information needed to complete the execution. + */ + protected Collection>> shardRequests(Collection> requests) { + return Collections.singletonList(requests); + } + + /** + * Function that returns the key used for matching returned objects against request argument types. + *

+ * The key returned from this function should match up with the key returned from {@link #getRequestArgumentKeySelector()}; + * + * @return key selector function + */ + protected abstract Func1 getBatchReturnTypeKeySelector(); + + /** + * Function that returns the key used for matching request arguments against returned objects. + *

+ * The key returned from this function should match up with the key returned from {@link #getBatchReturnTypeKeySelector()}; + * + * @return key selector function + */ + protected abstract Func1 getRequestArgumentKeySelector(); + + /** + * Invoked if a {@link CollapsedRequest} in the batch does not have a response set on it. + *

+ * This allows setting an exception (via {@link CollapsedRequest#setException(Exception)}) or a fallback response (via {@link CollapsedRequest#setResponse(Object)}). + * + * @param r {@link CollapsedRequest} + * that needs a response or exception set on it. + */ + protected abstract void onMissingResponse(CollapsedRequest r); + + /** + * Function for mapping from BatchReturnType to ResponseType. + *

+ * Often these two types are exactly the same so it's just a pass-thru. + * + * @return function for mapping from BatchReturnType to ResponseType + */ + protected abstract Func1 getBatchReturnTypeToResponseTypeMapper(); + + /** + * Used for asynchronous execution with a callback by subscribing to the {@link Observable}. + *

+ * This eagerly starts execution the same as {@link HystrixCollapser#queue()} and {@link HystrixCollapser#execute()}. + * A lazy {@link Observable} can be obtained from {@link #toObservable()}. + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#computation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ * Use {@link #toObservable(rx.Scheduler)} to schedule the callback differently. + *

+ * See https://github.com/Netflix/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of of {@link HystrixCommand}{@code } execution after mapping + * the {@code } into {@code } + */ + public Observable observe() { + // use a ReplaySubject to buffer the eagerly subscribed-to Observable + ReplaySubject subject = ReplaySubject.create(); + // eagerly kick off subscription + final Subscription underlyingSubscription = toObservable().subscribe(subject); + // return the subject that can be subscribed to later while the execution has already started + return subject.doOnUnsubscribe(new Action0() { + @Override + public void call() { + underlyingSubscription.unsubscribe(); + } + }); + } + + /** + * A lazy {@link Observable} that will execute when subscribed to. + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#computation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ *

+ * See https://github.com/Netflix/RxJava/wiki for more information. + * + * @return {@code Observable} that lazily executes and calls back with the result of of {@link HystrixCommand}{@code } execution after mapping the + * {@code } into {@code } + */ + public Observable toObservable() { + // when we callback with the data we want to do the work + // on a separate thread than the one giving us the callback + return toObservable(Schedulers.computation()); + } + + /** + * A lazy {@link Observable} that will execute when subscribed to. + *

+ * See https://github.com/Netflix/RxJava/wiki for more information. + * + * @param observeOn + * The {@link Scheduler} to execute callbacks on. + * @return {@code Observable} that lazily executes and calls back with the result of of {@link HystrixCommand}{@code } execution after mapping the + * {@code } into {@code } + */ + public Observable toObservable(Scheduler observeOn) { + + return Observable.defer(new Func0>() { + @Override + public Observable call() { + final boolean isRequestCacheEnabled = getProperties().requestCacheEnabled().get(); + + /* try from cache first */ + if (isRequestCacheEnabled) { + HystrixCachedObservable fromCache = requestCache.get(getCacheKey()); + if (fromCache != null) { + metrics.markResponseFromCache(); + return fromCache.toObservable(); + } + } + + RequestCollapser requestCollapser = collapserFactory.getRequestCollapser(collapserInstanceWrapper); + Observable response = requestCollapser.submitRequest(getRequestArgument()); + metrics.markRequestBatched(); + if (isRequestCacheEnabled) { + /* + * A race can occur here with multiple threads queuing but only one will be cached. + * This means we can have some duplication of requests in a thread-race but we're okay + * with having some inefficiency in duplicate requests in the same batch + * and then subsequent requests will retrieve a previously cached Observable. + * + * If this is an issue we can make a lazy-future that gets set in the cache + * then only the winning 'put' will be invoked to actually call 'submitRequest' + */ + HystrixCachedObservable toCache = HystrixCachedObservable.from(response); + HystrixCachedObservable fromCache = requestCache.putIfAbsent(getCacheKey(), toCache); + if (fromCache == null) { + return toCache.toObservable(); + } else { + return fromCache.toObservable(); + } + } + return response; + } + }); + } + + /** + * Key to be used for request caching. + *

+ * By default this returns null which means "do not cache". + *

+ * To enable caching override this method and return a string key uniquely representing the state of a command instance. + *

+ * If multiple command instances in the same request scope match keys then only the first will be executed and all others returned from cache. + * + * @return String cacheKey or null if not to cache + */ + protected String getCacheKey() { + return null; + } + + /** + * Clears all state. If new requests come in instances will be recreated and metrics started from scratch. + */ + /* package */static void reset() { + RequestCollapserFactory.reset(); + } + + private static String getDefaultNameFromClass(@SuppressWarnings("rawtypes") Class cls) { + String fromCache = defaultNameCache.get(cls); + if (fromCache != null) { + return fromCache; + } + // generate the default + // default HystrixCommandKey to use if the method is not overridden + String name = cls.getSimpleName(); + if (name.equals("")) { + // we don't have a SimpleName (anonymous inner class) so use the full class name + name = cls.getName(); + name = name.substring(name.lastIndexOf('.') + 1, name.length()); + } + defaultNameCache.put(cls, name); + return name; + } + + /** + * Fluent interface for arguments to the {@link HystrixObservableCollapser} constructor. + *

+ * The required arguments are set via the 'with' factory method and optional arguments via the 'and' chained methods. + *

+ * Example: + *

 {@code
+     *  Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("CollapserName"))
+                .andScope(Scope.REQUEST);
+     * } 
+ * + * @NotThreadSafe + */ + public static class Setter { + private final HystrixCollapserKey collapserKey; + private Scope scope = Scope.REQUEST; // default if nothing is set + private HystrixCollapserProperties.Setter propertiesSetter; + + private Setter(HystrixCollapserKey collapserKey) { + this.collapserKey = collapserKey; + } + + /** + * Setter factory method containing required values. + *

+ * All optional arguments can be set via the chained methods. + * + * @param collapserKey + * {@link HystrixCollapserKey} that identifies this collapser and provides the key used for retrieving properties, request caches, publishing metrics etc. + * @return Setter for fluent interface via method chaining + */ + public static Setter withCollapserKey(HystrixCollapserKey collapserKey) { + return new Setter(collapserKey); + } + + /** + * {@link Scope} defining what scope the collapsing should occur within + * + * @param scope collapser scope + * + * @return Setter for fluent interface via method chaining + */ + public Setter andScope(Scope scope) { + this.scope = scope; + return this; + } + + /** + * @param propertiesSetter + * {@link HystrixCollapserProperties.Setter} that allows instance specific property overrides (which can then be overridden by dynamic properties, see + * {@link HystrixPropertiesStrategy} for + * information on order of precedence). + *

+ * Will use defaults if left NULL. + * @return Setter for fluent interface via method chaining + */ + public Setter andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter propertiesSetter) { + this.propertiesSetter = propertiesSetter; + return this; + } + + } + + // this is a micro-optimization but saves about 1-2microseconds (on 2011 MacBook Pro) + // on the repetitive string processing that will occur on the same classes over and over again + @SuppressWarnings("rawtypes") + private static ConcurrentHashMap, String> defaultNameCache = new ConcurrentHashMap, String>(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java new file mode 100644 index 0000000..fe4d088 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java @@ -0,0 +1,259 @@ +/** + * Copyright 2012 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 rx.Observable; + +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; + +/** + * Used to wrap code that will execute potentially risky functionality (typically meaning a service call over the network) + * with fault and latency tolerance, statistics and performance metrics capture, circuit breaker and bulkhead functionality. + * This command should be used for a purely non-blocking call pattern. The caller of this command will be subscribed to the Observable returned by the run() method. + * + * @param + * the return type + * + * @ThreadSafe + */ +public abstract class HystrixObservableCommand extends AbstractCommand implements HystrixObservable, HystrixInvokableInfo { + + /** + * Construct a {@link HystrixObservableCommand} with defined {@link HystrixCommandGroupKey}. + *

+ * The {@link HystrixCommandKey} will be derived from the implementing class name. + * + * @param group + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixObservableCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace with, + * common business purpose etc. + */ + protected HystrixObservableCommand(HystrixCommandGroupKey group) { + // use 'null' to specify use the default + this(new Setter(group)); + } + + /** + * + * Overridden to true so that all onNext emissions are captured + * + * @return if onNext events should be reported on + * This affects {@link HystrixRequestLog}, and {@link HystrixEventNotifier} currently. Metrics/Hooks later + */ + @Override + protected boolean shouldOutputOnNextEvents() { + return true; + } + + @Override + protected String getFallbackMethodName() { + return "resumeWithFallback"; + } + + @Override + protected boolean isFallbackUserDefined() { + Boolean containsFromMap = commandContainsFallback.get(commandKey); + if (containsFromMap != null) { + return containsFromMap; + } else { + Boolean toInsertIntoMap; + try { + getClass().getDeclaredMethod("resumeWithFallback"); + toInsertIntoMap = true; + } catch (NoSuchMethodException nsme) { + toInsertIntoMap = false; + } + commandContainsFallback.put(commandKey, toInsertIntoMap); + return toInsertIntoMap; + } + } + + @Override + protected boolean commandIsScalar() { + return false; + } + + /** + * Construct a {@link HystrixObservableCommand} with defined {@link Setter} that allows injecting property and strategy overrides and other optional arguments. + *

+ * NOTE: The {@link HystrixCommandKey} is used to associate a {@link HystrixObservableCommand} with {@link HystrixCircuitBreaker}, {@link HystrixCommandMetrics} and other objects. + *

+ * Do not create multiple {@link HystrixObservableCommand} implementations with the same {@link HystrixCommandKey} but different injected default properties as the first instantiated will win. + *

+ * Properties passed in via {@link Setter#andCommandPropertiesDefaults} are cached for the given {@link HystrixCommandKey} for the life of the JVM + * or until {@link Hystrix#reset()} is called. Dynamic properties allow runtime changes. Read more on the Hystrix Wiki. + * + * @param setter + * Fluent interface for constructor arguments + */ + protected HystrixObservableCommand(Setter setter) { + // use 'null' to specify use the default + this(setter.groupKey, setter.commandKey, setter.threadPoolKey, null, null, setter.commandPropertiesDefaults, setter.threadPoolPropertiesDefaults, null, null, null, null, null); + } + + /** + * Allow constructing a {@link HystrixObservableCommand} with injection of most aspects of its functionality. + *

+ * Some of these never have a legitimate reason for injection except in unit testing. + *

+ * Most of the args will revert to a valid default if 'null' is passed in. + */ + HystrixObservableCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, + HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults, + HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore, + HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { + super(group, key, threadPoolKey, circuitBreaker, threadPool, commandPropertiesDefaults, threadPoolPropertiesDefaults, metrics, fallbackSemaphore, executionSemaphore, propertiesStrategy, executionHook); + } + + /** + * Fluent interface for arguments to the {@link HystrixObservableCommand} constructor. + *

+ * The required arguments are set via the 'with' factory method and optional arguments via the 'and' chained methods. + *

+ * Example: + *

 {@code
+     *  Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GroupName"))
+                .andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"))
+                .andEventNotifier(notifier);
+     * } 
+ * + * @NotThreadSafe + */ + final public static class Setter { + + protected final HystrixCommandGroupKey groupKey; + protected HystrixCommandKey commandKey; + protected HystrixThreadPoolKey threadPoolKey; + protected HystrixCommandProperties.Setter commandPropertiesDefaults; + protected HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults; + + /** + * Setter factory method containing required values. + *

+ * All optional arguments can be set via the chained methods. + * + * @param groupKey + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixObservableCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace + * with, + * common business purpose etc. + */ + protected Setter(HystrixCommandGroupKey groupKey) { + this.groupKey = groupKey; + + // default to using SEMAPHORE for ObservableCommand + commandPropertiesDefaults = setDefaults(HystrixCommandProperties.Setter()); + } + + /** + * Setter factory method with required values. + *

+ * All optional arguments can be set via the chained methods. + * + * @param groupKey + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixObservableCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace + * with, + * common business purpose etc. + */ + public static Setter withGroupKey(HystrixCommandGroupKey groupKey) { + return new Setter(groupKey); + } + + /** + * @param commandKey + * {@link HystrixCommandKey} used to identify a {@link HystrixObservableCommand} instance for statistics, circuit-breaker, properties, etc. + *

+ * By default this will be derived from the instance class name. + *

+ * NOTE: Every unique {@link HystrixCommandKey} will result in new instances of {@link HystrixCircuitBreaker}, {@link HystrixCommandMetrics} and {@link HystrixCommandProperties}. + * Thus, + * the number of variants should be kept to a finite and reasonable number to avoid high-memory usage or memory leacks. + *

+ * Hundreds of keys is fine, tens of thousands is probably not. + * @return Setter for fluent interface via method chaining + */ + public Setter andCommandKey(HystrixCommandKey commandKey) { + this.commandKey = commandKey; + return this; + } + + /** + * Optional + * + * @param commandPropertiesDefaults + * {@link HystrixCommandProperties.Setter} with property overrides for this specific instance of {@link HystrixObservableCommand}. + *

+ * See the {@link HystrixPropertiesStrategy} JavaDocs for more information on properties and order of precedence. + * @return Setter for fluent interface via method chaining + */ + public Setter andCommandPropertiesDefaults(HystrixCommandProperties.Setter commandPropertiesDefaults) { + this.commandPropertiesDefaults = setDefaults(commandPropertiesDefaults); + return this; + } + + private HystrixCommandProperties.Setter setDefaults(HystrixCommandProperties.Setter commandPropertiesDefaults) { + if (commandPropertiesDefaults.getExecutionIsolationStrategy() == null) { + // default to using SEMAPHORE for ObservableCommand if the user didn't set it + commandPropertiesDefaults.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE); + } + return commandPropertiesDefaults; + } + + } + + /** + * Implement this method with code to be executed when {@link #observe()} or {@link #toObservable()} are invoked. + * + * @return R response type + */ + protected abstract Observable construct(); + + /** + * If {@link #observe()} or {@link #toObservable()} fails in any way then this method will be invoked to provide an opportunity to return a fallback response. + *

+ * This should do work that does not require network transport to produce. + *

+ * In other words, this should be a static or cached result that can immediately be returned upon failure. + *

+ * If network traffic is wanted for fallback (such as going to MemCache) then the fallback implementation should invoke another {@link HystrixObservableCommand} instance that protects against + * that network + * access and possibly has another level of fallback that does not involve network access. + *

+ * DEFAULT BEHAVIOR: It throws UnsupportedOperationException. + * + * @return R or UnsupportedOperationException if not implemented + */ + protected Observable resumeWithFallback() { + return Observable.error(new UnsupportedOperationException("No fallback available.")); + } + + @Override + final protected Observable getExecutionObservable() { + return construct(); + } + + @Override + final protected Observable getFallbackObservable() { + return resumeWithFallback(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestCache.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestCache.java new file mode 100644 index 0000000..0d0d6e2 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestCache.java @@ -0,0 +1,282 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableHolder; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.internal.operators.CachedObservable; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Cache that is scoped to the current request as managed by {@link HystrixRequestVariableDefault}. + *

+ * This is used for short-lived caching of {@link HystrixCommand} instances to allow de-duping of command executions within a request. + */ +public class HystrixRequestCache { + @SuppressWarnings("unused") + private static final Logger logger = LoggerFactory.getLogger(HystrixRequestCache.class); + + // the String key must be: HystrixRequestCache.prefix + concurrencyStrategy + cacheKey + private final static ConcurrentHashMap caches = new ConcurrentHashMap(); + + private final RequestCacheKey rcKey; + private final HystrixConcurrencyStrategy concurrencyStrategy; + + /** + * A ConcurrentHashMap per 'prefix' and per request scope that is used to to dedupe requests in the same request. + *

+ * Key => CommandPrefix + CacheKey : Future from queue() + */ + private static final HystrixRequestVariableHolder>> requestVariableForCache = new HystrixRequestVariableHolder>>(new HystrixRequestVariableLifecycle>>() { + + @Override + public ConcurrentHashMap> initialValue() { + return new ConcurrentHashMap>(); + } + + @Override + public void shutdown(ConcurrentHashMap> value) { + // nothing to shutdown + } + + }); + + private HystrixRequestCache(RequestCacheKey rcKey, HystrixConcurrencyStrategy concurrencyStrategy) { + this.rcKey = rcKey; + this.concurrencyStrategy = concurrencyStrategy; + } + + public static HystrixRequestCache getInstance(HystrixCommandKey key, HystrixConcurrencyStrategy concurrencyStrategy) { + return getInstance(new RequestCacheKey(key, concurrencyStrategy), concurrencyStrategy); + } + + public static HystrixRequestCache getInstance(HystrixCollapserKey key, HystrixConcurrencyStrategy concurrencyStrategy) { + return getInstance(new RequestCacheKey(key, concurrencyStrategy), concurrencyStrategy); + } + + private static HystrixRequestCache getInstance(RequestCacheKey rcKey, HystrixConcurrencyStrategy concurrencyStrategy) { + HystrixRequestCache c = caches.get(rcKey); + if (c == null) { + HystrixRequestCache newRequestCache = new HystrixRequestCache(rcKey, concurrencyStrategy); + HystrixRequestCache existing = caches.putIfAbsent(rcKey, newRequestCache); + if (existing == null) { + // we won so use the new one + c = newRequestCache; + } else { + // we lost so use the existing + c = existing; + } + } + return c; + } + + /** + * Retrieve a cached Future for this request scope if a matching command has already been executed/queued. + * + * @return {@code Future} + */ + // suppressing warnings because we are using a raw Future since it's in a heterogeneous ConcurrentHashMap cache + @SuppressWarnings({ "unchecked" }) + /* package */ HystrixCachedObservable get(String cacheKey) { + ValueCacheKey key = getRequestCacheKey(cacheKey); + if (key != null) { + ConcurrentHashMap> cacheInstance = requestVariableForCache.get(concurrencyStrategy); + if (cacheInstance == null) { + throw new IllegalStateException("Request caching is not available. Maybe you need to initialize the HystrixRequestContext?"); + } + /* look for the stored value */ + return (HystrixCachedObservable) cacheInstance.get(key); + } + return null; + } + + /** + * Put the Future in the cache if it does not already exist. + *

+ * If this method returns a non-null value then another thread won the race and it should be returned instead of proceeding with execution of the new Future. + * + * @param cacheKey + * key as defined by {@link HystrixCommand#getCacheKey()} + * @param f + * Future to be cached + * + * @return null if nothing else was in the cache (or this {@link HystrixCommand} does not have a cacheKey) or previous value if another thread beat us to adding to the cache + */ + // suppressing warnings because we are using a raw Future since it's in a heterogeneous ConcurrentHashMap cache + @SuppressWarnings({ "unchecked" }) + /* package */ HystrixCachedObservable putIfAbsent(String cacheKey, HystrixCachedObservable f) { + ValueCacheKey key = getRequestCacheKey(cacheKey); + if (key != null) { + /* look for the stored value */ + ConcurrentHashMap> cacheInstance = requestVariableForCache.get(concurrencyStrategy); + if (cacheInstance == null) { + throw new IllegalStateException("Request caching is not available. Maybe you need to initialize the HystrixRequestContext?"); + } + HystrixCachedObservable alreadySet = (HystrixCachedObservable) cacheInstance.putIfAbsent(key, f); + if (alreadySet != null) { + // someone beat us so we didn't cache this + return alreadySet; + } + } + // we either set it in the cache or do not have a cache key + return null; + } + + /** + * Clear the cache for a given cacheKey. + * + * @param cacheKey + * key as defined by {@link HystrixCommand#getCacheKey()} + */ + public void clear(String cacheKey) { + ValueCacheKey key = getRequestCacheKey(cacheKey); + if (key != null) { + ConcurrentHashMap> cacheInstance = requestVariableForCache.get(concurrencyStrategy); + if (cacheInstance == null) { + throw new IllegalStateException("Request caching is not available. Maybe you need to initialize the HystrixRequestContext?"); + } + + /* remove this cache key */ + cacheInstance.remove(key); + } + } + + /** + * Request CacheKey: HystrixRequestCache.prefix + concurrencyStrategy + HystrixCommand.getCacheKey (as injected via get/put to this class) + *

+ * We prefix with {@link HystrixCommandKey} or {@link HystrixCollapserKey} since the cache is heterogeneous and we don't want to accidentally return cached Futures from different + * types. + * + * @return ValueCacheKey + */ + private ValueCacheKey getRequestCacheKey(String cacheKey) { + if (cacheKey != null) { + /* create the cache key we will use to retrieve/store that include the type key prefix */ + return new ValueCacheKey(rcKey, cacheKey); + } + return null; + } + + private static class ValueCacheKey { + private final RequestCacheKey rvKey; + private final String valueCacheKey; + + private ValueCacheKey(RequestCacheKey rvKey, String valueCacheKey) { + this.rvKey = rvKey; + this.valueCacheKey = valueCacheKey; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((rvKey == null) ? 0 : rvKey.hashCode()); + result = prime * result + ((valueCacheKey == null) ? 0 : valueCacheKey.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ValueCacheKey other = (ValueCacheKey) obj; + if (rvKey == null) { + if (other.rvKey != null) + return false; + } else if (!rvKey.equals(other.rvKey)) + return false; + if (valueCacheKey == null) { + if (other.valueCacheKey != null) + return false; + } else if (!valueCacheKey.equals(other.valueCacheKey)) + return false; + return true; + } + + } + + private static class RequestCacheKey { + private final short type; // used to differentiate between Collapser/Command if key is same between them + private final String key; + private final HystrixConcurrencyStrategy concurrencyStrategy; + + private RequestCacheKey(HystrixCommandKey commandKey, HystrixConcurrencyStrategy concurrencyStrategy) { + type = 1; + if (commandKey == null) { + this.key = null; + } else { + this.key = commandKey.name(); + } + this.concurrencyStrategy = concurrencyStrategy; + } + + private RequestCacheKey(HystrixCollapserKey collapserKey, HystrixConcurrencyStrategy concurrencyStrategy) { + type = 2; + if (collapserKey == null) { + this.key = null; + } else { + this.key = collapserKey.name(); + } + this.concurrencyStrategy = concurrencyStrategy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((concurrencyStrategy == null) ? 0 : concurrencyStrategy.hashCode()); + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + type; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RequestCacheKey other = (RequestCacheKey) obj; + if (type != other.type) + return false; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + if (concurrencyStrategy == null) { + if (other.concurrencyStrategy != null) + return false; + } else if (!concurrencyStrategy.equals(other.concurrencyStrategy)) + return false; + return true; + } + + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java new file mode 100644 index 0000000..e9131df --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java @@ -0,0 +1,267 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.metric.HystrixRequestEventsStream; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableHolder; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Log of {@link HystrixCommand} executions and events during the current request. + */ +public class HystrixRequestLog { + private static final Logger logger = LoggerFactory.getLogger(HystrixRequestLog.class); + + /** + * RequestLog: Reduce Chance of Memory Leak + * https://github.com/Netflix/Hystrix/issues/53 + * + * Upper limit on RequestLog before ignoring further additions and logging warnings. + * + * Intended to help prevent memory leaks when someone isn't aware of the + * HystrixRequestContext lifecycle or enabling/disabling RequestLog. + */ + /* package */static final int MAX_STORAGE = 1000; + + private static final HystrixRequestVariableHolder currentRequestLog = new HystrixRequestVariableHolder(new HystrixRequestVariableLifecycle() { + @Override + public HystrixRequestLog initialValue() { + return new HystrixRequestLog(); + } + + public void shutdown(HystrixRequestLog value) { + //write this value to the Request stream + HystrixRequestEventsStream.getInstance().write(value.getAllExecutedCommands()); + } + }); + + /** + * History of {@link HystrixCommand} executed in this request. + */ + private LinkedBlockingQueue> executedCommands = new LinkedBlockingQueue>(MAX_STORAGE); + + /** + * History of {@link HystrixInvokableInfo} executed in this request. + */ + private LinkedBlockingQueue> allExecutedCommands = new LinkedBlockingQueue>(MAX_STORAGE); + + // prevent public instantiation + private HystrixRequestLog() { + } + + /** + * {@link HystrixRequestLog} for current request as defined by {@link HystrixRequestContext}. + * + * @return {@link HystrixRequestLog} + */ + public static HystrixRequestLog getCurrentRequest(HystrixConcurrencyStrategy concurrencyStrategy) { + return currentRequestLog.get(concurrencyStrategy); + } + + /** + * {@link HystrixRequestLog} for current request as defined by {@link HystrixRequestContext}. + *

+ * NOTE: This uses the default {@link HystrixConcurrencyStrategy} or global override. If an injected strategy is being used by commands you must instead use + * {@link #getCurrentRequest(HystrixConcurrencyStrategy)}. + * + * @return {@link HystrixRequestLog} + */ + public static HystrixRequestLog getCurrentRequest() { + return currentRequestLog.get(HystrixPlugins.getInstance().getConcurrencyStrategy()); + } + + /** + * Retrieve {@link HystrixCommand} instances that were executed during this {@link HystrixRequestContext}. + * + * @return {@code Collection>} + */ + @Deprecated + public Collection> getExecutedCommands() { + return Collections.unmodifiableCollection(executedCommands); + } + + /** + * Retrieve {@link HystrixCommand} instances that were executed during this {@link HystrixRequestContext}. + * + * @return {@code Collection>} + */ + public Collection> getAllExecutedCommands() { + return Collections.unmodifiableCollection(allExecutedCommands); + } + + /** + * Add {@link HystrixCommand} instance to the request log. + * + * @param command + * {@code HystrixCommand} + */ + /* package */void addExecutedCommand(HystrixInvokableInfo command) { + if (!allExecutedCommands.offer(command)) { + // see RequestLog: Reduce Chance of Memory Leak https://github.com/Netflix/Hystrix/issues/53 + logger.warn("RequestLog ignoring command after reaching limit of " + MAX_STORAGE + ". See https://github.com/Netflix/Hystrix/issues/53 for more information."); + } + + // TODO remove this when deprecation completed + if (command instanceof HystrixCommand) { + @SuppressWarnings("rawtypes") + HystrixCommand _c = (HystrixCommand) command; + if (!executedCommands.offer(_c)) { + // see RequestLog: Reduce Chance of Memory Leak https://github.com/Netflix/Hystrix/issues/53 + logger.warn("RequestLog ignoring command after reaching limit of " + MAX_STORAGE + ". See https://github.com/Netflix/Hystrix/issues/53 for more information."); + } + } + } + + /** + * Formats the log of executed commands into a string usable for logging purposes. + *

+ * Examples: + *

    + *
  • TestCommand[SUCCESS][1ms]
  • + *
  • TestCommand[SUCCESS][1ms], TestCommand[SUCCESS, RESPONSE_FROM_CACHE][1ms]x4
  • + *
  • TestCommand[TIMEOUT][1ms]
  • + *
  • TestCommand[FAILURE][1ms]
  • + *
  • TestCommand[THREAD_POOL_REJECTED][1ms]
  • + *
  • TestCommand[THREAD_POOL_REJECTED, FALLBACK_SUCCESS][1ms]
  • + *
  • TestCommand[EMIT, SUCCESS][1ms]
  • + *
  • TestCommand[EMITx5, SUCCESS][1ms]
  • + *
  • TestCommand[EMITx5, FAILURE, FALLBACK_EMITx6, FALLBACK_FAILURE][100ms]
  • + *
  • TestCommand[FAILURE, FALLBACK_SUCCESS][1ms], TestCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][1ms]x4
  • + *
  • GetData[SUCCESS][1ms], PutData[SUCCESS][1ms], GetValues[SUCCESS][1ms], GetValues[SUCCESS, RESPONSE_FROM_CACHE][1ms], TestCommand[FAILURE, FALLBACK_FAILURE][1ms], TestCommand[FAILURE, + * FALLBACK_FAILURE, RESPONSE_FROM_CACHE][1ms]
  • + *
+ *

+ * If a command has a multiplier such as x4, that means this command was executed 4 times with the same events. The time in milliseconds is the sum of the 4 executions. + *

+ * For example, TestCommand[SUCCESS][15ms]x4 represents TestCommand being executed 4 times and the sum of those 4 executions was 15ms. These 4 each executed the run() method since + * RESPONSE_FROM_CACHE was not present as an event. + * + * If an EMIT or FALLBACK_EMIT has a multiplier such as x5, that means a HystrixObservableCommand was used and it emitted that number of OnNexts. + *

+ * For example, TestCommand[EMITx5, FAILURE, FALLBACK_EMITx6, FALLBACK_FAILURE][100ms] represents TestCommand executing observably, emitted 5 OnNexts, then an OnError. + * This command also has an Observable fallback, and it emits 6 OnNexts, then an OnCompleted. + * + * @return String request log or "Unknown" if unable to instead of throwing an exception. + */ + public String getExecutedCommandsAsString() { + try { + LinkedHashMap aggregatedCommandsExecuted = new LinkedHashMap(); + Map aggregatedCommandExecutionTime = new HashMap(); + + StringBuilder builder = new StringBuilder(); + int estimatedLength = 0; + for (HystrixInvokableInfo command : allExecutedCommands) { + builder.setLength(0); + builder.append(command.getCommandKey().name()); + + List events = new ArrayList(command.getExecutionEvents()); + if (events.size() > 0) { + Collections.sort(events); + //replicate functionality of Arrays.toString(events.toArray()) to append directly to existing StringBuilder + builder.append("["); + for (HystrixEventType event : events) { + switch (event) { + case EMIT: + int numEmissions = command.getNumberEmissions(); + if (numEmissions > 1) { + builder.append(event).append("x").append(numEmissions).append(", "); + } else { + builder.append(event).append(", "); + } + break; + case FALLBACK_EMIT: + int numFallbackEmissions = command.getNumberFallbackEmissions(); + if (numFallbackEmissions > 1) { + builder.append(event).append("x").append(numFallbackEmissions).append(", "); + } else { + builder.append(event).append(", "); + } + break; + default: + builder.append(event).append(", "); + } + } + builder.setCharAt(builder.length() - 2, ']'); + builder.setLength(builder.length() - 1); + } else { + builder.append("[Executed]"); + } + + String display = builder.toString(); + estimatedLength += display.length() + 12; //add 12 chars to display length for appending totalExecutionTime and count below + Integer counter = aggregatedCommandsExecuted.get(display); + if( counter != null){ + aggregatedCommandsExecuted.put(display, counter + 1); + } else { + // add it + aggregatedCommandsExecuted.put(display, 1); + } + + int executionTime = command.getExecutionTimeInMilliseconds(); + if (executionTime < 0) { + // do this so we don't create negative values or subtract values + executionTime = 0; + } + counter = aggregatedCommandExecutionTime.get(display); + if( counter != null && executionTime > 0){ + // add to the existing executionTime (sum of executionTimes for duplicate command displayNames) + aggregatedCommandExecutionTime.put(display, aggregatedCommandExecutionTime.get(display) + executionTime); + } else { + // add it + aggregatedCommandExecutionTime.put(display, executionTime); + } + + } + + builder.setLength(0); + builder.ensureCapacity(estimatedLength); + for (String displayString : aggregatedCommandsExecuted.keySet()) { + if (builder.length() > 0) { + builder.append(", "); + } + builder.append(displayString); + + int totalExecutionTime = aggregatedCommandExecutionTime.get(displayString); + builder.append("[").append(totalExecutionTime).append("ms]"); + + int count = aggregatedCommandsExecuted.get(displayString); + if (count > 1) { + builder.append("x").append(count); + } + } + return builder.toString(); + } catch (Exception e) { + logger.error("Failed to create HystrixRequestLog response header string.", e); + // don't let this cause the entire app to fail so just return "Unknown" + return "Unknown"; + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPool.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPool.java new file mode 100644 index 0000000..c68641d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPool.java @@ -0,0 +1,275 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherFactory; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Scheduler; +import rx.functions.Func0; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * ThreadPool used to executed {@link HystrixCommand#run()} on separate threads when configured to do so with {@link HystrixCommandProperties#executionIsolationStrategy()}. + *

+ * Typically each {@link HystrixCommandGroupKey} has its own thread-pool so that any one group of commands can not starve others from being able to run. + *

+ * A {@link HystrixCommand} can be configured with a thread-pool explicitly by injecting a {@link HystrixThreadPoolKey} or via the + * {@link HystrixCommandProperties#executionIsolationThreadPoolKeyOverride()} otherwise it + * will derive a {@link HystrixThreadPoolKey} from the injected {@link HystrixCommandGroupKey}. + *

+ * The pool should be sized large enough to handle normal healthy traffic but small enough that it will constrain concurrent execution if backend calls become latent. + *

+ * For more information see the Github Wiki: https://github.com/Netflix/Hystrix/wiki/Configuration#wiki-ThreadPool and https://github.com/Netflix/Hystrix/wiki/How-it-Works#wiki-Isolation + */ +public interface HystrixThreadPool { + + /** + * Implementation of {@link ThreadPoolExecutor}. + * + * @return ThreadPoolExecutor + */ + public ExecutorService getExecutor(); + + public Scheduler getScheduler(); + + public Scheduler getScheduler(Func0 shouldInterruptThread); + + /** + * Mark when a thread begins executing a command. + */ + public void markThreadExecution(); + + /** + * Mark when a thread completes executing a command. + */ + public void markThreadCompletion(); + + /** + * Mark when a command gets rejected from the threadpool + */ + public void markThreadRejection(); + + /** + * Whether the queue will allow adding an item to it. + *

+ * This allows dynamic control of the max queueSize versus whatever the actual max queueSize is so that dynamic changes can be done via property changes rather than needing an app + * restart to adjust when commands should be rejected from queuing up. + * + * @return boolean whether there is space on the queue + */ + public boolean isQueueSpaceAvailable(); + + /** + * @ExcludeFromJavadoc + */ + /* package */static class Factory { + /* + * Use the String from HystrixThreadPoolKey.name() instead of the HystrixThreadPoolKey instance as it's just an interface and we can't ensure the object + * we receive implements hashcode/equals correctly and do not want the default hashcode/equals which would create a new threadpool for every object we get even if the name is the same + */ + /* package */final static ConcurrentHashMap threadPools = new ConcurrentHashMap(); + + /** + * Get the {@link HystrixThreadPool} instance for a given {@link HystrixThreadPoolKey}. + *

+ * This is thread-safe and ensures only 1 {@link HystrixThreadPool} per {@link HystrixThreadPoolKey}. + * + * @return {@link HystrixThreadPool} instance + */ + /* package */static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) { + // get the key to use instead of using the object itself so that if people forget to implement equals/hashcode things will still work + String key = threadPoolKey.name(); + + // this should find it for all but the first time + HystrixThreadPool previouslyCached = threadPools.get(key); + if (previouslyCached != null) { + return previouslyCached; + } + + // if we get here this is the first time so we need to initialize + synchronized (HystrixThreadPool.class) { + if (!threadPools.containsKey(key)) { + threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder)); + } + } + return threadPools.get(key); + } + + /** + * Initiate the shutdown of all {@link HystrixThreadPool} instances. + *

+ * NOTE: This is NOT thread-safe if HystrixCommands are concurrently being executed + * and causing thread-pools to initialize while also trying to shutdown. + *

+ */ + /* package */static synchronized void shutdown() { + for (HystrixThreadPool pool : threadPools.values()) { + pool.getExecutor().shutdown(); + } + threadPools.clear(); + } + + /** + * Initiate the shutdown of all {@link HystrixThreadPool} instances and wait up to the given time on each pool to complete. + *

+ * NOTE: This is NOT thread-safe if HystrixCommands are concurrently being executed + * and causing thread-pools to initialize while also trying to shutdown. + *

+ */ + /* package */static synchronized void shutdown(long timeout, TimeUnit unit) { + for (HystrixThreadPool pool : threadPools.values()) { + pool.getExecutor().shutdown(); + } + for (HystrixThreadPool pool : threadPools.values()) { + try { + while (! pool.getExecutor().awaitTermination(timeout, unit)) { + } + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while waiting for thread-pools to terminate. Pools may not be correctly shutdown or cleared.", e); + } + } + threadPools.clear(); + } + } + + /** + * @ExcludeFromJavadoc + * @ThreadSafe + */ + /* package */static class HystrixThreadPoolDefault implements HystrixThreadPool { + private static final Logger logger = LoggerFactory.getLogger(HystrixThreadPoolDefault.class); + + private final HystrixThreadPoolProperties properties; + private final BlockingQueue queue; + private final ThreadPoolExecutor threadPool; + private final HystrixThreadPoolMetrics metrics; + private final int queueSize; + + public HystrixThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesDefaults) { + this.properties = HystrixPropertiesFactory.getThreadPoolProperties(threadPoolKey, propertiesDefaults); + HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy(); + this.queueSize = properties.maxQueueSize().get(); + + this.metrics = HystrixThreadPoolMetrics.getInstance(threadPoolKey, + concurrencyStrategy.getThreadPool(threadPoolKey, properties), + properties); + this.threadPool = this.metrics.getThreadPool(); + this.queue = this.threadPool.getQueue(); + + /* strategy: HystrixMetricsPublisherThreadPool */ + HystrixMetricsPublisherFactory.createOrRetrievePublisherForThreadPool(threadPoolKey, this.metrics, this.properties); + } + + @Override + public ThreadPoolExecutor getExecutor() { + touchConfig(); + return threadPool; + } + + @Override + public Scheduler getScheduler() { + //by default, interrupt underlying threads on timeout + return getScheduler(new Func0() { + @Override + public Boolean call() { + return true; + } + }); + } + + @Override + public Scheduler getScheduler(Func0 shouldInterruptThread) { + touchConfig(); + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread); + } + + // allow us to change things via fast-properties by setting it each time + private void touchConfig() { + final int dynamicCoreSize = properties.coreSize().get(); + final int configuredMaximumSize = properties.maximumSize().get(); + int dynamicMaximumSize = properties.actualMaximumSize(); + final boolean allowSizesToDiverge = properties.getAllowMaximumSizeToDivergeFromCoreSize().get(); + boolean maxTooLow = false; + + if (allowSizesToDiverge && configuredMaximumSize < dynamicCoreSize) { + //if user sets maximum < core (or defaults get us there), we need to maintain invariant of core <= maximum + dynamicMaximumSize = dynamicCoreSize; + maxTooLow = true; + } + + // In JDK 6, setCorePoolSize and setMaximumPoolSize will execute a lock operation. Avoid them if the pool size is not changed. + if (threadPool.getCorePoolSize() != dynamicCoreSize || (allowSizesToDiverge && threadPool.getMaximumPoolSize() != dynamicMaximumSize)) { + if (maxTooLow) { + logger.error("Hystrix ThreadPool configuration for : " + metrics.getThreadPoolKey().name() + " is trying to set coreSize = " + + dynamicCoreSize + " and maximumSize = " + configuredMaximumSize + ". Maximum size will be set to " + + dynamicMaximumSize + ", the coreSize value, since it must be equal to or greater than the coreSize value"); + } + threadPool.setCorePoolSize(dynamicCoreSize); + threadPool.setMaximumPoolSize(dynamicMaximumSize); + } + + threadPool.setKeepAliveTime(properties.keepAliveTimeMinutes().get(), TimeUnit.MINUTES); + } + + @Override + public void markThreadExecution() { + metrics.markThreadExecution(); + } + + @Override + public void markThreadCompletion() { + metrics.markThreadCompletion(); + } + + @Override + public void markThreadRejection() { + metrics.markThreadRejection(); + } + + /** + * Whether the threadpool queue has space available according to the queueSizeRejectionThreshold settings. + * + * Note that the queueSize is an final instance variable on HystrixThreadPoolDefault, and not looked up dynamically. + * The data structure is static, so this does not make sense as a dynamic lookup. + * The queueSizeRejectionThreshold can be dynamic (up to queueSize), so that should + * still get checked on each invocation. + *

+ * If a SynchronousQueue implementation is used (maxQueueSize <= 0), it always returns 0 as the size so this would always return true. + */ + @Override + public boolean isQueueSpaceAvailable() { + if (queueSize <= 0) { + // we don't have a queue so we won't look for space but instead + // let the thread-pool reject or not + return true; + } else { + return threadPool.getQueue().size() < properties.queueSizeRejectionThreshold().get(); + } + } + + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolKey.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolKey.java new file mode 100644 index 0000000..14cf4f8 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolKey.java @@ -0,0 +1,60 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.util.InternMap; + +/** + * A key to represent a {@link HystrixThreadPool} for monitoring, metrics publishing, caching and other such uses. + *

+ * This interface is intended to work natively with Enums so that implementing code can be an enum that implements this interface. + */ +public interface HystrixThreadPoolKey extends HystrixKey { + class Factory { + private Factory() { + } + + // used to intern instances so we don't keep re-creating them millions of times for the same key + private static final InternMap intern + = new InternMap( + new InternMap.ValueConstructor() { + @Override + public HystrixThreadPoolKey create(String key) { + return new HystrixThreadPoolKeyDefault(key); + } + }); + + /** + * Retrieve (or create) an interned HystrixThreadPoolKey instance for a given name. + * + * @param name thread pool name + * @return HystrixThreadPoolKey instance that is interned (cached) so a given name will always retrieve the same instance. + */ + public static HystrixThreadPoolKey asKey(String name) { + return intern.interned(name); + } + + private static class HystrixThreadPoolKeyDefault extends HystrixKeyDefault implements HystrixThreadPoolKey { + public HystrixThreadPoolKeyDefault(String name) { + super(name); + } + } + + /* package-private */ static int getThreadPoolCount() { + return intern.size(); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolMetrics.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolMetrics.java new file mode 100644 index 0000000..22e978b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolMetrics.java @@ -0,0 +1,364 @@ +/** + * Copyright 2012 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 com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.consumer.CumulativeThreadPoolEventCounterStream; +import com.netflix.hystrix.metric.consumer.RollingThreadPoolMaxConcurrencyStream; +import com.netflix.hystrix.metric.consumer.RollingThreadPoolEventCounterStream; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; +import rx.functions.Func2; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Used by {@link HystrixThreadPool} to record metrics. + */ +public class HystrixThreadPoolMetrics extends HystrixMetrics { + + private static final HystrixEventType[] ALL_COMMAND_EVENT_TYPES = HystrixEventType.values(); + private static final HystrixEventType.ThreadPool[] ALL_THREADPOOL_EVENT_TYPES = HystrixEventType.ThreadPool.values(); + private static final int NUMBER_THREADPOOL_EVENT_TYPES = ALL_THREADPOOL_EVENT_TYPES.length; + + // String is HystrixThreadPoolKey.name() (we can't use HystrixThreadPoolKey directly as we can't guarantee it implements hashcode/equals correctly) + private static final ConcurrentHashMap metrics = new ConcurrentHashMap(); + + /** + * Get or create the {@link HystrixThreadPoolMetrics} instance for a given {@link HystrixThreadPoolKey}. + *

+ * This is thread-safe and ensures only 1 {@link HystrixThreadPoolMetrics} per {@link HystrixThreadPoolKey}. + * + * @param key + * {@link HystrixThreadPoolKey} of {@link HystrixThreadPool} instance requesting the {@link HystrixThreadPoolMetrics} + * @param threadPool + * Pass-thru of ThreadPoolExecutor to {@link HystrixThreadPoolMetrics} instance on first time when constructed + * @param properties + * Pass-thru to {@link HystrixThreadPoolMetrics} instance on first time when constructed + * @return {@link HystrixThreadPoolMetrics} + */ + public static HystrixThreadPoolMetrics getInstance(HystrixThreadPoolKey key, ThreadPoolExecutor threadPool, HystrixThreadPoolProperties properties) { + // attempt to retrieve from cache first + HystrixThreadPoolMetrics threadPoolMetrics = metrics.get(key.name()); + if (threadPoolMetrics != null) { + return threadPoolMetrics; + } else { + synchronized (HystrixThreadPoolMetrics.class) { + HystrixThreadPoolMetrics existingMetrics = metrics.get(key.name()); + if (existingMetrics != null) { + return existingMetrics; + } else { + HystrixThreadPoolMetrics newThreadPoolMetrics = new HystrixThreadPoolMetrics(key, threadPool, properties); + metrics.putIfAbsent(key.name(), newThreadPoolMetrics); + return newThreadPoolMetrics; + } + } + } + } + + /** + * Get the {@link HystrixThreadPoolMetrics} instance for a given {@link HystrixThreadPoolKey} or null if one does not exist. + * + * @param key + * {@link HystrixThreadPoolKey} of {@link HystrixThreadPool} instance requesting the {@link HystrixThreadPoolMetrics} + * @return {@link HystrixThreadPoolMetrics} + */ + public static HystrixThreadPoolMetrics getInstance(HystrixThreadPoolKey key) { + return metrics.get(key.name()); + } + + /** + * All registered instances of {@link HystrixThreadPoolMetrics} + * + * @return {@code Collection} + */ + public static Collection getInstances() { + List threadPoolMetrics = new ArrayList(); + for (HystrixThreadPoolMetrics tpm: metrics.values()) { + if (hasExecutedCommandsOnThread(tpm)) { + threadPoolMetrics.add(tpm); + } + } + + return Collections.unmodifiableCollection(threadPoolMetrics); + } + + private static boolean hasExecutedCommandsOnThread(HystrixThreadPoolMetrics threadPoolMetrics) { + return threadPoolMetrics.getCurrentCompletedTaskCount().intValue() > 0; + } + + public static final Func2 appendEventToBucket + = new Func2() { + @Override + public long[] call(long[] initialCountArray, HystrixCommandCompletion execution) { + ExecutionResult.EventCounts eventCounts = execution.getEventCounts(); + for (HystrixEventType eventType: ALL_COMMAND_EVENT_TYPES) { + long eventCount = eventCounts.getCount(eventType); + HystrixEventType.ThreadPool threadPoolEventType = HystrixEventType.ThreadPool.from(eventType); + if (threadPoolEventType != null) { + initialCountArray[threadPoolEventType.ordinal()] += eventCount; + } + } + return initialCountArray; + } + }; + + public static final Func2 counterAggregator = new Func2() { + @Override + public long[] call(long[] cumulativeEvents, long[] bucketEventCounts) { + for (int i = 0; i < NUMBER_THREADPOOL_EVENT_TYPES; i++) { + cumulativeEvents[i] += bucketEventCounts[i]; + } + return cumulativeEvents; + } + }; + + /** + * Clears all state from metrics. If new requests come in instances will be recreated and metrics started from scratch. + * + */ + /* package */ static void reset() { + metrics.clear(); + } + + private final HystrixThreadPoolKey threadPoolKey; + private final ThreadPoolExecutor threadPool; + private final HystrixThreadPoolProperties properties; + + private final AtomicInteger concurrentExecutionCount = new AtomicInteger(); + + private final RollingThreadPoolEventCounterStream rollingCounterStream; + private final CumulativeThreadPoolEventCounterStream cumulativeCounterStream; + private final RollingThreadPoolMaxConcurrencyStream rollingThreadPoolMaxConcurrencyStream; + + private HystrixThreadPoolMetrics(HystrixThreadPoolKey threadPoolKey, ThreadPoolExecutor threadPool, HystrixThreadPoolProperties properties) { + super(null); + this.threadPoolKey = threadPoolKey; + this.threadPool = threadPool; + this.properties = properties; + + rollingCounterStream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, properties); + cumulativeCounterStream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, properties); + rollingThreadPoolMaxConcurrencyStream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, properties); + } + + /** + * {@link ThreadPoolExecutor} this executor represents. + * + * @return ThreadPoolExecutor + */ + public ThreadPoolExecutor getThreadPool() { + return threadPool; + } + + /** + * {@link HystrixThreadPoolKey} these metrics represent. + * + * @return HystrixThreadPoolKey + */ + public HystrixThreadPoolKey getThreadPoolKey() { + return threadPoolKey; + } + + /** + * {@link HystrixThreadPoolProperties} of the {@link HystrixThreadPool} these metrics represent. + * + * @return HystrixThreadPoolProperties + */ + public HystrixThreadPoolProperties getProperties() { + return properties; + } + + /** + * Value from {@link ThreadPoolExecutor#getActiveCount()} + * + * @return Number + */ + public Number getCurrentActiveCount() { + return threadPool.getActiveCount(); + } + + /** + * Value from {@link ThreadPoolExecutor#getCompletedTaskCount()} + * + * @return Number + */ + public Number getCurrentCompletedTaskCount() { + return threadPool.getCompletedTaskCount(); + } + + /** + * Value from {@link ThreadPoolExecutor#getCorePoolSize()} + * + * @return Number + */ + public Number getCurrentCorePoolSize() { + return threadPool.getCorePoolSize(); + } + + /** + * Value from {@link ThreadPoolExecutor#getLargestPoolSize()} + * + * @return Number + */ + public Number getCurrentLargestPoolSize() { + return threadPool.getLargestPoolSize(); + } + + /** + * Value from {@link ThreadPoolExecutor#getMaximumPoolSize()} + * + * @return Number + */ + public Number getCurrentMaximumPoolSize() { + return threadPool.getMaximumPoolSize(); + } + + /** + * Value from {@link ThreadPoolExecutor#getPoolSize()} + * + * @return Number + */ + public Number getCurrentPoolSize() { + return threadPool.getPoolSize(); + } + + /** + * Value from {@link ThreadPoolExecutor#getTaskCount()} + * + * @return Number + */ + public Number getCurrentTaskCount() { + return threadPool.getTaskCount(); + } + + /** + * Current size of {@link BlockingQueue} used by the thread-pool + * + * @return Number + */ + public Number getCurrentQueueSize() { + return threadPool.getQueue().size(); + } + + /** + * Invoked each time a thread is executed. + */ + public void markThreadExecution() { + concurrentExecutionCount.incrementAndGet(); + } + + /** + * Rolling count of number of threads executed during rolling statistical window. + *

+ * The rolling window is defined by {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowInMilliseconds()}. + * + * @return rolling count of threads executed + */ + public long getRollingCountThreadsExecuted() { + return rollingCounterStream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED); + } + + /** + * Cumulative count of number of threads executed since the start of the application. + * + * @return cumulative count of threads executed + */ + public long getCumulativeCountThreadsExecuted() { + return cumulativeCounterStream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED); + } + + /** + * Rolling count of number of threads rejected during rolling statistical window. + *

+ * The rolling window is defined by {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowInMilliseconds()}. + * + * @return rolling count of threads rejected + */ + public long getRollingCountThreadsRejected() { + return rollingCounterStream.getLatestCount(HystrixEventType.ThreadPool.REJECTED); + } + + /** + * Cumulative count of number of threads rejected since the start of the application. + * + * @return cumulative count of threads rejected + */ + public long getCumulativeCountThreadsRejected() { + return cumulativeCounterStream.getLatestCount(HystrixEventType.ThreadPool.REJECTED); + } + + public long getRollingCount(HystrixEventType.ThreadPool event) { + return rollingCounterStream.getLatestCount(event); + } + + public long getCumulativeCount(HystrixEventType.ThreadPool event) { + return cumulativeCounterStream.getLatestCount(event); + } + + @Override + public long getCumulativeCount(HystrixRollingNumberEvent event) { + return cumulativeCounterStream.getLatestCount(HystrixEventType.ThreadPool.from(event)); + } + + @Override + public long getRollingCount(HystrixRollingNumberEvent event) { + return rollingCounterStream.getLatestCount(HystrixEventType.ThreadPool.from(event)); + } + + /** + * Invoked each time a thread completes. + */ + public void markThreadCompletion() { + concurrentExecutionCount.decrementAndGet(); + } + + /** + * Rolling max number of active threads during rolling statistical window. + *

+ * The rolling window is defined by {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowInMilliseconds()}. + * + * @return rolling max active threads + */ + public long getRollingMaxActiveThreads() { + return rollingThreadPoolMaxConcurrencyStream.getLatestRollingMax(); + } + + /** + * Invoked each time a command is rejected from the thread-pool + */ + public void markThreadRejection() { + concurrentExecutionCount.decrementAndGet(); + } + + public static Func0 getCurrentConcurrencyThunk(final HystrixThreadPoolKey threadPoolKey) { + return new Func0() { + @Override + public Integer call() { + return HystrixThreadPoolMetrics.getInstance(threadPoolKey).concurrentExecutionCount.get(); + } + }; + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolProperties.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolProperties.java new file mode 100644 index 0000000..56f4e63 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolProperties.java @@ -0,0 +1,328 @@ +/** + * Copyright 2012 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 static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forBoolean; +import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forInteger; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.HystrixRollingNumber; + +/** + * Properties for instances of {@link HystrixThreadPool}. + *

+ * Default implementation of methods uses Archaius (https://github.com/Netflix/archaius) + * + * Note a change in behavior in 1.5.7. Prior to that version, the configuration for 'coreSize' was used to control + * both coreSize and maximumSize. This is a fixed-size threadpool that can never give up an unused thread. In 1.5.7+, + * the values can diverge, and if you set coreSize < maximumSize, threads can be given up (subject to the keep-alive + * time) + * + * It is OK to leave maximumSize unset using any version of Hystrix. If you do, then maximum size will default to + * core size and you'll have a fixed-size threadpool. + * + * If you accidentally set maximumSize < coreSize, then maximum will be raised to coreSize + * (this prioritizes keeping extra threads around rather than inducing threadpool rejections) + */ +public abstract class HystrixThreadPoolProperties { + + /* defaults */ + static int default_coreSize = 10; // core size of thread pool + static int default_maximumSize = 10; // maximum size of thread pool + static int default_keepAliveTimeMinutes = 1; // minutes to keep a thread alive + static int default_maxQueueSize = -1; // size of queue (this can't be dynamically changed so we use 'queueSizeRejectionThreshold' to artificially limit and reject) + // -1 turns it off and makes us use SynchronousQueue + static boolean default_allow_maximum_size_to_diverge_from_core_size = false; //should the maximumSize config value get read and used in configuring the threadPool + //turning this on should be a conscious decision by the user, so we default it to false + + static int default_queueSizeRejectionThreshold = 5; // number of items in queue + static int default_threadPoolRollingNumberStatisticalWindow = 10000; // milliseconds for rolling number + static int default_threadPoolRollingNumberStatisticalWindowBuckets = 10; // number of buckets in rolling number (10 1-second buckets) + + private final HystrixProperty corePoolSize; + private final HystrixProperty maximumPoolSize; + private final HystrixProperty keepAliveTime; + private final HystrixProperty maxQueueSize; + private final HystrixProperty queueSizeRejectionThreshold; + private final HystrixProperty allowMaximumSizeToDivergeFromCoreSize; + + private final HystrixProperty threadPoolRollingNumberStatisticalWindowInMilliseconds; + private final HystrixProperty threadPoolRollingNumberStatisticalWindowBuckets; + + protected HystrixThreadPoolProperties(HystrixThreadPoolKey key) { + this(key, new Setter(), "hystrix"); + } + + protected HystrixThreadPoolProperties(HystrixThreadPoolKey key, Setter builder) { + this(key, builder, "hystrix"); + } + + protected HystrixThreadPoolProperties(HystrixThreadPoolKey key, Setter builder, String propertyPrefix) { + this.allowMaximumSizeToDivergeFromCoreSize = getProperty(propertyPrefix, key, "allowMaximumSizeToDivergeFromCoreSize", + builder.getAllowMaximumSizeToDivergeFromCoreSize(), default_allow_maximum_size_to_diverge_from_core_size); + + this.corePoolSize = getProperty(propertyPrefix, key, "coreSize", builder.getCoreSize(), default_coreSize); + //this object always contains a reference to the configuration value for the maximumSize of the threadpool + //it only gets applied if allowMaximumSizeToDivergeFromCoreSize is true + this.maximumPoolSize = getProperty(propertyPrefix, key, "maximumSize", builder.getMaximumSize(), default_maximumSize); + + this.keepAliveTime = getProperty(propertyPrefix, key, "keepAliveTimeMinutes", builder.getKeepAliveTimeMinutes(), default_keepAliveTimeMinutes); + this.maxQueueSize = getProperty(propertyPrefix, key, "maxQueueSize", builder.getMaxQueueSize(), default_maxQueueSize); + this.queueSizeRejectionThreshold = getProperty(propertyPrefix, key, "queueSizeRejectionThreshold", builder.getQueueSizeRejectionThreshold(), default_queueSizeRejectionThreshold); + this.threadPoolRollingNumberStatisticalWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingStats.timeInMilliseconds", builder.getMetricsRollingStatisticalWindowInMilliseconds(), default_threadPoolRollingNumberStatisticalWindow); + this.threadPoolRollingNumberStatisticalWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingStats.numBuckets", builder.getMetricsRollingStatisticalWindowBuckets(), default_threadPoolRollingNumberStatisticalWindowBuckets); + } + + private static HystrixProperty getProperty(String propertyPrefix, HystrixThreadPoolKey key, String instanceProperty, Integer builderOverrideValue, Integer defaultValue) { + return forInteger() + .add(propertyPrefix + ".threadpool." + key.name() + "." + instanceProperty, builderOverrideValue) + .add(propertyPrefix + ".threadpool.default." + instanceProperty, defaultValue) + .build(); + } + + private static HystrixProperty getProperty(String propertyPrefix, HystrixThreadPoolKey key, String instanceProperty, Boolean builderOverrideValue, Boolean defaultValue) { + return forBoolean() + .add(propertyPrefix + ".threadpool." + key.name() + "." + instanceProperty, builderOverrideValue) + .add(propertyPrefix + ".threadpool.default." + instanceProperty, defaultValue) + .build(); + } + + /** + * Core thread-pool size that gets passed to {@link ThreadPoolExecutor#setCorePoolSize(int)} + * + * @return {@code HystrixProperty} + */ + public HystrixProperty coreSize() { + return corePoolSize; + } + + /** + * Maximum thread-pool size configured for threadpool. May conflict with other config, so if you need the + * actual value that gets passed to {@link ThreadPoolExecutor#setMaximumPoolSize(int)}, use {@link #actualMaximumSize()} + * + * @return {@code HystrixProperty} + */ + public HystrixProperty maximumSize() { + return maximumPoolSize; + } + + /** + * Given all of the thread pool configuration, what is the actual maximumSize applied to the thread pool + * via {@link ThreadPoolExecutor#setMaximumPoolSize(int)} + * + * Cases: + * 1) allowMaximumSizeToDivergeFromCoreSize == false: maximumSize is set to coreSize + * 2) allowMaximumSizeToDivergeFromCoreSize == true, maximumSize >= coreSize: thread pool has different core/max sizes, so return the configured max + * 3) allowMaximumSizeToDivergeFromCoreSize == true, maximumSize < coreSize: threadpool incorrectly configured, use coreSize for max size + * @return actually configured maximum size of threadpool + */ + public Integer actualMaximumSize() { + final int coreSize = coreSize().get(); + final int maximumSize = maximumSize().get(); + if (getAllowMaximumSizeToDivergeFromCoreSize().get()) { + if (coreSize > maximumSize) { + return coreSize; + } else { + return maximumSize; + } + } else { + return coreSize; + } + } + + /** + * Keep-alive time in minutes that gets passed to {@link ThreadPoolExecutor#setKeepAliveTime(long, TimeUnit)} + * + * @return {@code HystrixProperty} + */ + public HystrixProperty keepAliveTimeMinutes() { + return keepAliveTime; + } + + /** + * Max queue size that gets passed to {@link BlockingQueue} in {@link HystrixConcurrencyStrategy#getBlockingQueue(int)} + * + * This should only affect the instantiation of a threadpool - it is not eliglible to change a queue size on the fly. + * For that, use {@link #queueSizeRejectionThreshold()}. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty maxQueueSize() { + return maxQueueSize; + } + + /** + * Queue size rejection threshold is an artificial "max" size at which rejections will occur even if {@link #maxQueueSize} has not been reached. This is done because the {@link #maxQueueSize} of a + * {@link BlockingQueue} can not be dynamically changed and we want to support dynamically changing the queue size that affects rejections. + *

+ * This is used by {@link HystrixCommand} when queuing a thread for execution. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty queueSizeRejectionThreshold() { + return queueSizeRejectionThreshold; + } + + public HystrixProperty getAllowMaximumSizeToDivergeFromCoreSize() { + return allowMaximumSizeToDivergeFromCoreSize; + } + + /** + * Duration of statistical rolling window in milliseconds. This is passed into {@link HystrixRollingNumber} inside each {@link HystrixThreadPoolMetrics} instance. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingStatisticalWindowInMilliseconds() { + return threadPoolRollingNumberStatisticalWindowInMilliseconds; + } + + /** + * Number of buckets the rolling statistical window is broken into. This is passed into {@link HystrixRollingNumber} inside each {@link HystrixThreadPoolMetrics} instance. + * + * @return {@code HystrixProperty} + */ + public HystrixProperty metricsRollingStatisticalWindowBuckets() { + return threadPoolRollingNumberStatisticalWindowBuckets; + } + + /** + * Factory method to retrieve the default Setter. + */ + public static Setter Setter() { + return new Setter(); + } + + /** + * Factory method to retrieve the default Setter. + * Groovy has a bug (GROOVY-6286) which does not allow method names and inner classes to have the same name + * This method fixes Issue #967 and allows Groovy consumers to choose this method and not trigger the bug + */ + public static Setter defaultSetter() { + return Setter(); + } + + /** + * Fluent interface that allows chained setting of properties that can be passed into a {@link HystrixThreadPool} via a {@link HystrixCommand} constructor to inject instance specific property + * overrides. + *

+ * See {@link HystrixPropertiesStrategy} for more information on order of precedence. + *

+ * Example: + *

+ *

 {@code
+     * HystrixThreadPoolProperties.Setter()
+     *           .withCoreSize(10)
+     *           .withQueueSizeRejectionThreshold(10);
+     * } 
+ * + * @NotThreadSafe + */ + public static class Setter { + private Integer coreSize = null; + private Integer maximumSize = null; + private Integer keepAliveTimeMinutes = null; + private Integer maxQueueSize = null; + private Integer queueSizeRejectionThreshold = null; + private Boolean allowMaximumSizeToDivergeFromCoreSize = null; + private Integer rollingStatisticalWindowInMilliseconds = null; + private Integer rollingStatisticalWindowBuckets = null; + + private Setter() { + } + + public Integer getCoreSize() { + return coreSize; + } + + public Integer getMaximumSize() { + return maximumSize; + } + + public Integer getKeepAliveTimeMinutes() { + return keepAliveTimeMinutes; + } + + public Integer getMaxQueueSize() { + return maxQueueSize; + } + + public Integer getQueueSizeRejectionThreshold() { + return queueSizeRejectionThreshold; + } + + public Boolean getAllowMaximumSizeToDivergeFromCoreSize() { + return allowMaximumSizeToDivergeFromCoreSize; + } + + public Integer getMetricsRollingStatisticalWindowInMilliseconds() { + return rollingStatisticalWindowInMilliseconds; + } + + public Integer getMetricsRollingStatisticalWindowBuckets() { + return rollingStatisticalWindowBuckets; + } + + public Setter withCoreSize(int value) { + this.coreSize = value; + return this; + } + + public Setter withMaximumSize(int value) { + this.maximumSize = value; + return this; + } + + public Setter withKeepAliveTimeMinutes(int value) { + this.keepAliveTimeMinutes = value; + return this; + } + + public Setter withMaxQueueSize(int value) { + this.maxQueueSize = value; + return this; + } + + public Setter withQueueSizeRejectionThreshold(int value) { + this.queueSizeRejectionThreshold = value; + return this; + } + + public Setter withAllowMaximumSizeToDivergeFromCoreSize(boolean value) { + this.allowMaximumSizeToDivergeFromCoreSize = value; + return this; + } + + public Setter withMetricsRollingStatisticalWindowInMilliseconds(int value) { + this.rollingStatisticalWindowInMilliseconds = value; + return this; + } + + public Setter withMetricsRollingStatisticalWindowBuckets(int value) { + this.rollingStatisticalWindowBuckets = value; + return this; + } + + + + + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixTimerThreadPoolProperties.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixTimerThreadPoolProperties.java new file mode 100644 index 0000000..5a3b49c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixTimerThreadPoolProperties.java @@ -0,0 +1,72 @@ +package com.netflix.hystrix; + +import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forInteger; + +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.strategy.properties.HystrixProperty; + +/** + * Properties for Hystrix timer thread pool. + *

+ * Default implementation of methods uses Archaius (https://github.com/Netflix/archaius) + */ +public abstract class HystrixTimerThreadPoolProperties { + + private final HystrixProperty corePoolSize; + + protected HystrixTimerThreadPoolProperties() { + this(new Setter().withCoreSize(Runtime.getRuntime().availableProcessors())); + } + + protected HystrixTimerThreadPoolProperties(Setter setter) { + this.corePoolSize = getProperty("hystrix", "coreSize", setter.getCoreSize()); + } + + private static HystrixProperty getProperty(String propertyPrefix, String instanceProperty, Integer defaultValue) { + + return forInteger() + .add(propertyPrefix + ".timer.threadpool.default." + instanceProperty, defaultValue) + .build(); + } + + public HystrixProperty getCorePoolSize() { + return corePoolSize; + } + + /** + * Factory method to retrieve the default Setter. + */ + public static Setter Setter() { + return new Setter(); + } + + /** + * Fluent interface that allows chained setting of properties. + *

+ * See {@link HystrixPropertiesStrategy} for more information on order of precedence. + *

+ * Example: + *

+ *

 {@code
+     * HystrixTimerThreadPoolProperties.Setter()
+     *           .withCoreSize(10);
+     * } 
+ * + * @NotThreadSafe + */ + public static class Setter { + private Integer coreSize = null; + + private Setter() { + } + + public Integer getCoreSize() { + return coreSize; + } + + public Setter withCoreSize(int value) { + this.coreSize = value; + return this; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapsedRequestSubject.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapsedRequestSubject.java new file mode 100644 index 0000000..b31f036 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapsedRequestSubject.java @@ -0,0 +1,185 @@ +/** + * 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.collapser; + +import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; +import rx.Observable; +import rx.functions.Action0; +import rx.subjects.ReplaySubject; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * The Observable that represents a collapsed request sent back to a user. It gets used by Collapser implementations + * when receiving a batch response and emitting values/errors to collapsers. + * + * There are 4 methods that Collapser implementations may use: + * + * 1) {@link #setResponse(T)}: return a single-valued response. equivalent to OnNext(T), OnCompleted() + * 2) {@link #emitResponse(T)}: emit a single value. equivalent to OnNext(T) + * 3) {@link #setException(Exception)}: return an exception. equivalent to OnError(Exception) + * 4) {@link #setComplete()}: mark that no more values will be emitted. Should be used in conjunction with {@link #emitResponse(T)}. equivalent to OnCompleted() + * + *

+ * This is an internal implementation of CollapsedRequest functionality. Instead of directly extending {@link rx.Observable}, + * it provides a {@link #toObservable()} method + *

+ * + * @param + * + * @param + */ +/* package */class CollapsedRequestSubject implements CollapsedRequest { + private final R argument; + + private AtomicBoolean valueSet = new AtomicBoolean(false); + private final ReplaySubject subject = ReplaySubject.create(); + private final Observable subjectWithAccounting; + + private volatile int outstandingSubscriptions = 0; + + public CollapsedRequestSubject(final R arg, final RequestBatch containingBatch) { + if (arg == RequestCollapser.NULL_SENTINEL) { + this.argument = null; + } else { + this.argument = arg; + } + this.subjectWithAccounting = subject + .doOnSubscribe(new Action0() { + @Override + public void call() { + outstandingSubscriptions++; + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + outstandingSubscriptions--; + if (outstandingSubscriptions == 0) { + containingBatch.remove(arg); + } + } + }); + } + + public CollapsedRequestSubject(final R arg) { + this.subjectWithAccounting = subject; + this.argument = arg; + } + + /** + * The request argument. + * + * @return request argument + */ + @Override + public R getArgument() { + return argument; + } + + /** + * When set any client thread blocking on get() will immediately be unblocked and receive the single-valued response. + * + * @throws IllegalStateException + * if called more than once or after setException. + * @param response response to give to initial command + */ + @Override + public void setResponse(T response) { + if (!isTerminated()) { + subject.onNext(response); + valueSet.set(true); + subject.onCompleted(); + } else { + throw new IllegalStateException("Response has already terminated so response can not be set : " + response); + } + } + + /** + * Emit a response that should be OnNexted to an Observer + * @param response response to emit to initial command + */ + @Override + public void emitResponse(T response) { + if (!isTerminated()) { + subject.onNext(response); + valueSet.set(true); + } else { + throw new IllegalStateException("Response has already terminated so response can not be set : " + response); + } + } + + @Override + public void setComplete() { + if (!isTerminated()) { + subject.onCompleted(); + } + } + + /** + * Set an exception if a response is not yet received otherwise skip it + * + * @param e synthetic error to set on initial command when no actual response is available + */ + public void setExceptionIfResponseNotReceived(Exception e) { + if (!valueSet.get() && !isTerminated()) { + subject.onError(e); + } + } + + /** + * Set an ISE if a response is not yet received otherwise skip it + * + * @param e A pre-generated exception. If this is null an ISE will be created and returned + * @param exceptionMessage The message for the ISE + */ + public Exception setExceptionIfResponseNotReceived(Exception e, String exceptionMessage) { + Exception exception = e; + + if (!valueSet.get() && !isTerminated()) { + if (e == null) { + exception = new IllegalStateException(exceptionMessage); + } + setExceptionIfResponseNotReceived(exception); + } + // return any exception that was generated + return exception; + } + + /** + * When set any client thread blocking on get() will immediately be unblocked and receive the exception. + * + * @throws IllegalStateException + * if called more than once or after setResponse. + * @param e received exception that gets set on the initial command + */ + @Override + public void setException(Exception e) { + if (!isTerminated()) { + subject.onError(e); + } else { + throw new IllegalStateException("Response has already terminated so exception can not be set", e); + } + } + + private boolean isTerminated() { + return (subject.hasCompleted() || subject.hasThrowable()); + } + + public Observable toObservable() { + return subjectWithAccounting; + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapserTimer.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapserTimer.java new file mode 100644 index 0000000..709292c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapserTimer.java @@ -0,0 +1,28 @@ +/** + * 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.collapser; + +import java.lang.ref.Reference; + +import com.netflix.hystrix.util.HystrixTimer.TimerListener; + +/** + * Timer used for trigger batch execution. + */ +public interface CollapserTimer { + + public Reference addListener(TimerListener collapseTask); +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/HystrixCollapserBridge.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/HystrixCollapserBridge.java new file mode 100644 index 0000000..958414e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/HystrixCollapserBridge.java @@ -0,0 +1,42 @@ +/** + * 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.collapser; + +import java.util.Collection; + +import rx.Observable; + +import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; +import com.netflix.hystrix.HystrixCollapserKey; + +/** + * Bridge between HystrixCollapser and RequestCollapser to expose 'protected' and 'private' functionality across packages. + * + * @param + * @param + * @param + */ +public interface HystrixCollapserBridge { + + Collection>> shardRequests(Collection> requests); + + Observable createObservableCommand(Collection> requests); + + Observable mapResponseToRequests(Observable batchResponse, Collection> requests); + + HystrixCollapserKey getCollapserKey(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/README.txt b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/README.txt new file mode 100644 index 0000000..f0ee0fb --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/README.txt @@ -0,0 +1,19 @@ +==== + Copyright 2016 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. +==== + +This package is not part of the public API and can change at any time. Do not rely upon any classes in this package. + +The public API is com.netflix.hystrix.HystrixCollapser \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RealCollapserTimer.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RealCollapserTimer.java new file mode 100644 index 0000000..3f1e645 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RealCollapserTimer.java @@ -0,0 +1,35 @@ +/** + * 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.collapser; + +import java.lang.ref.Reference; + +import com.netflix.hystrix.util.HystrixTimer; +import com.netflix.hystrix.util.HystrixTimer.TimerListener; + +/** + * Actual CollapserTimer implementation for triggering batch execution that uses HystrixTimer. + */ +public class RealCollapserTimer implements CollapserTimer { + /* single global timer that all collapsers will schedule their tasks on */ + private final static HystrixTimer timer = HystrixTimer.getInstance(); + + @Override + public Reference addListener(TimerListener collapseTask) { + return timer.addTimerListener(collapseTask); + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestBatch.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestBatch.java new file mode 100644 index 0000000..9858292 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestBatch.java @@ -0,0 +1,289 @@ +/** + * 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.collapser; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Action1; + +import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; +import com.netflix.hystrix.HystrixCollapserProperties; + +/** + * A batch of requests collapsed together by a RequestCollapser instance. When full or time has expired it will execute and stop accepting further submissions. + * + * @param + * @param + * @param + */ +public class RequestBatch { + + private static final Logger logger = LoggerFactory.getLogger(RequestBatch.class); + + private final HystrixCollapserBridge commandCollapser; + private final int maxBatchSize; + private final AtomicBoolean batchStarted = new AtomicBoolean(); + + private final ConcurrentMap> argumentMap = + new ConcurrentHashMap>(); + private final HystrixCollapserProperties properties; + + private ReentrantReadWriteLock batchLock = new ReentrantReadWriteLock(); + + public RequestBatch(HystrixCollapserProperties properties, HystrixCollapserBridge commandCollapser, int maxBatchSize) { + this.properties = properties; + this.commandCollapser = commandCollapser; + this.maxBatchSize = maxBatchSize; + } + + /** + * @return Observable if offer accepted, null if batch is full, already started or completed + */ + public Observable offer(RequestArgumentType arg) { + /* short-cut - if the batch is started we reject the offer */ + if (batchStarted.get()) { + return null; + } + + /* + * The 'read' just means non-exclusive even though we are writing. + */ + if (batchLock.readLock().tryLock()) { + try { + /* double-check now that we have the lock - if the batch is started we reject the offer */ + if (batchStarted.get()) { + return null; + } + + if (argumentMap.size() >= maxBatchSize) { + return null; + } else { + CollapsedRequestSubject collapsedRequest = + new CollapsedRequestSubject(arg, this); + final CollapsedRequestSubject existing = (CollapsedRequestSubject) argumentMap.putIfAbsent(arg, collapsedRequest); + /** + * If the argument already exists in the batch, then there are 2 options: + * A) If request caching is ON (the default): only keep 1 argument in the batch and let all responses + * be hooked up to that argument + * B) If request caching is OFF: return an error to all duplicate argument requests + * + * This maintains the invariant that each batch has no duplicate arguments. This prevents the impossible + * logic (in a user-provided mapResponseToRequests for HystrixCollapser and the internals of HystrixObservableCollapser) + * of trying to figure out which argument of a set of duplicates should get attached to a response. + * + * See https://github.com/Netflix/Hystrix/pull/1176 for further discussion. + */ + if (existing != null) { + boolean requestCachingEnabled = properties.requestCacheEnabled().get(); + if (requestCachingEnabled) { + return existing.toObservable(); + } else { + return Observable.error(new IllegalArgumentException("Duplicate argument in collapser batch : [" + arg + "] This is not supported. Please turn request-caching on for HystrixCollapser:" + commandCollapser.getCollapserKey().name() + " or prevent duplicates from making it into the batch!")); + } + } else { + return collapsedRequest.toObservable(); + } + + } + } finally { + batchLock.readLock().unlock(); + } + } else { + return null; + } + } + + /** + * Best-effort attempt to remove an argument from a batch. This may get invoked when a cancellation occurs somewhere downstream. + * This method finds the argument in the batch, and removes it. + * + * @param arg argument to remove from batch + */ + /* package-private */ void remove(RequestArgumentType arg) { + if (batchStarted.get()) { + //nothing we can do + return; + } + + if (batchLock.readLock().tryLock()) { + try { + /* double-check now that we have the lock - if the batch is started, deleting is useless */ + if (batchStarted.get()) { + return; + } + + argumentMap.remove(arg); + } finally { + batchLock.readLock().unlock(); + } + } + } + + /** + * Collapsed requests are triggered for batch execution and the array of arguments is passed in. + *

+ * IMPORTANT IMPLEMENTATION DETAILS => The expected contract (responsibilities) of this method implementation is: + *

+ *

    + *
  • Do NOT block => Do the work on a separate worker thread. Do not perform inline otherwise it will block other requests.
  • + *
  • Set ALL CollapsedRequest response values => Set the response values T on each CollapsedRequest, even if the response is NULL otherwise the user thread waiting on the response will + * think a response was never received and will either block indefinitely or will timeout while waiting.
  • + *
+ * + */ + public void executeBatchIfNotAlreadyStarted() { + /* + * - check that we only execute once since there's multiple paths to do so (timer, waiting thread or max batch size hit) + * - close the gate so 'offer' can no longer be invoked and we turn those threads away so they create a new batch + */ + if (batchStarted.compareAndSet(false, true)) { + /* wait for 'offer'/'remove' threads to finish before executing the batch so 'requests' is complete */ + batchLock.writeLock().lock(); + + try { + // shard batches + Collection>> shards = commandCollapser.shardRequests(argumentMap.values()); + // for each shard execute its requests + for (final Collection> shardRequests : shards) { + try { + // create a new command to handle this batch of requests + Observable o = commandCollapser.createObservableCommand(shardRequests); + + commandCollapser.mapResponseToRequests(o, shardRequests).doOnError(new Action1() { + + /** + * This handles failed completions + */ + @Override + public void call(Throwable e) { + // handle Throwable in case anything is thrown so we don't block Observers waiting for onError/onCompleted + Exception ee; + if (e instanceof Exception) { + ee = (Exception) e; + } else { + ee = new RuntimeException("Throwable caught while executing batch and mapping responses.", e); + } + logger.debug("Exception mapping responses to requests.", e); + // if a failure occurs we want to pass that exception to all of the Futures that we've returned + for (CollapsedRequest request : argumentMap.values()) { + try { + ((CollapsedRequestSubject) request).setExceptionIfResponseNotReceived(ee); + } catch (IllegalStateException e2) { + // if we have partial responses set in mapResponseToRequests + // then we may get IllegalStateException as we loop over them + // so we'll log but continue to the rest + logger.error("Partial success of 'mapResponseToRequests' resulted in IllegalStateException while setting Exception. Continuing ... ", e2); + } + } + } + + }).doOnCompleted(new Action0() { + + /** + * This handles successful completions + */ + @Override + public void call() { + // check that all requests had setResponse or setException invoked in case 'mapResponseToRequests' was implemented poorly + Exception e = null; + for (CollapsedRequest request : shardRequests) { + try { + e = ((CollapsedRequestSubject) request).setExceptionIfResponseNotReceived(e,"No response set by " + commandCollapser.getCollapserKey().name() + " 'mapResponseToRequests' implementation."); + } catch (IllegalStateException e2) { + logger.debug("Partial success of 'mapResponseToRequests' resulted in IllegalStateException while setting 'No response set' Exception. Continuing ... ", e2); + } + } + } + + }).subscribe(); + + } catch (Exception e) { + logger.error("Exception while creating and queueing command with batch.", e); + // if a failure occurs we want to pass that exception to all of the Futures that we've returned + for (CollapsedRequest request : shardRequests) { + try { + request.setException(e); + } catch (IllegalStateException e2) { + logger.debug("Failed trying to setException on CollapsedRequest", e2); + } + } + } + } + + } catch (Exception e) { + logger.error("Exception while sharding requests.", e); + // same error handling as we do around the shards, but this is a wider net in case the shardRequest method fails + for (CollapsedRequest request : argumentMap.values()) { + try { + request.setException(e); + } catch (IllegalStateException e2) { + logger.debug("Failed trying to setException on CollapsedRequest", e2); + } + } + } finally { + batchLock.writeLock().unlock(); + } + } + } + + public void shutdown() { + // take the 'batchStarted' state so offers and execution will not be triggered elsewhere + if (batchStarted.compareAndSet(false, true)) { + // get the write lock so offers are synced with this (we don't really need to unlock as this is a one-shot deal to shutdown) + batchLock.writeLock().lock(); + try { + // if we win the 'start' and once we have the lock we can now shut it down otherwise another thread will finish executing this batch + if (argumentMap.size() > 0) { + logger.warn("Requests still exist in queue but will not be executed due to RequestCollapser shutdown: " + argumentMap.size(), new IllegalStateException()); + /* + * In the event that there is a concurrency bug or thread scheduling prevents the timer from ticking we need to handle this so the Future.get() calls do not block. + * + * I haven't been able to reproduce this use case on-demand but when stressing a machine saw this occur briefly right after the JVM paused (logs stopped scrolling). + * + * This safety-net just prevents the CollapsedRequestFutureImpl.get() from waiting on the CountDownLatch until its max timeout. + */ + for (CollapsedRequest request : argumentMap.values()) { + try { + ((CollapsedRequestSubject) request).setExceptionIfResponseNotReceived(new IllegalStateException("Requests not executed before shutdown.")); + } catch (Exception e) { + logger.debug("Failed to setException on CollapsedRequestFutureImpl instances.", e); + } + /** + * https://github.com/Netflix/Hystrix/issues/78 Include more info when collapsed requests remain in queue + */ + logger.warn("Request still in queue but not be executed due to RequestCollapser shutdown. Argument => " + request.getArgument() + " Request Object => " + request, new IllegalStateException()); + } + + } + } finally { + batchLock.writeLock().unlock(); + } + } + } + + public int getSize() { + return argumentMap.size(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapser.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapser.java new file mode 100644 index 0000000..783297d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapser.java @@ -0,0 +1,186 @@ +/** + * 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.collapser; + +import java.lang.ref.Reference; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import rx.Observable; + +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixContextCallable; +import com.netflix.hystrix.util.HystrixTimer.TimerListener; + +/** + * Requests are submitted to this and batches executed based on size or time. Scoped to either a request or the global application. + *

+ * Instances of this are retrieved from the RequestCollapserFactory. + * + * Must be thread-safe since it exists within a RequestVariable which is request-scoped and can be accessed from multiple threads. + * + * @ThreadSafe + */ +public class RequestCollapser { + static final Logger logger = LoggerFactory.getLogger(RequestCollapser.class); + static final Object NULL_SENTINEL = new Object(); + + private final HystrixCollapserBridge commandCollapser; + // batch can be null once shutdown + private final AtomicReference> batch = new AtomicReference>(); + private final AtomicReference> timerListenerReference = new AtomicReference>(); + private final AtomicBoolean timerListenerRegistered = new AtomicBoolean(); + private final CollapserTimer timer; + private final HystrixCollapserProperties properties; + private final HystrixConcurrencyStrategy concurrencyStrategy; + + /** + * @param commandCollapser collapser which will create the batched requests and demultiplex the results + * @param properties collapser properties that define how collapsing occurs + * @param timer {@link CollapserTimer} which performs the collapsing + * @param concurrencyStrategy strategy for managing the {@link Callable}s generated by {@link RequestCollapser} + */ + RequestCollapser(HystrixCollapserBridge commandCollapser, HystrixCollapserProperties properties, CollapserTimer timer, HystrixConcurrencyStrategy concurrencyStrategy) { + this.commandCollapser = commandCollapser; // the command with implementation of abstract methods we need + this.concurrencyStrategy = concurrencyStrategy; + this.properties = properties; + this.timer = timer; + batch.set(new RequestBatch(properties, commandCollapser, properties.maxRequestsInBatch().get())); + } + + /** + * Submit a request to a batch. If the batch maxSize is hit trigger the batch immediately. + * + * @param arg argument to a {@link RequestCollapser} + * @return Observable + * @throws IllegalStateException + * if submitting after shutdown + */ + public Observable submitRequest(final RequestArgumentType arg) { + /* + * We only want the timer ticking if there are actually things to do so we register it the first time something is added. + */ + if (!timerListenerRegistered.get() && timerListenerRegistered.compareAndSet(false, true)) { + /* schedule the collapsing task to be executed every x milliseconds (x defined inside CollapsedTask) */ + timerListenerReference.set(timer.addListener(new CollapsedTask())); + } + + // loop until succeed (compare-and-set spin-loop) + while (true) { + final RequestBatch b = batch.get(); + if (b == null) { + return Observable.error(new IllegalStateException("Submitting requests after collapser is shutdown")); + } + + final Observable response; + if (arg != null) { + response = b.offer(arg); + } else { + response = b.offer( (RequestArgumentType) NULL_SENTINEL); + } + // it will always get an Observable unless we hit the max batch size + if (response != null) { + return response; + } else { + // this batch can't accept requests so create a new one and set it if another thread doesn't beat us + createNewBatchAndExecutePreviousIfNeeded(b); + } + } + } + + private void createNewBatchAndExecutePreviousIfNeeded(RequestBatch previousBatch) { + if (previousBatch == null) { + throw new IllegalStateException("Trying to start null batch which means it was shutdown already."); + } + if (batch.compareAndSet(previousBatch, new RequestBatch(properties, commandCollapser, properties.maxRequestsInBatch().get()))) { + // this thread won so trigger the previous batch + previousBatch.executeBatchIfNotAlreadyStarted(); + } + } + + /** + * Called from RequestVariable.shutdown() to unschedule the task. + */ + public void shutdown() { + RequestBatch currentBatch = batch.getAndSet(null); + if (currentBatch != null) { + currentBatch.shutdown(); + } + + if (timerListenerReference.get() != null) { + // if the timer was started we'll clear it so it stops ticking + timerListenerReference.get().clear(); + } + } + + /** + * Executed on each Timer interval execute the current batch if it has requests in it. + */ + private class CollapsedTask implements TimerListener { + final Callable callableWithContextOfParent; + + CollapsedTask() { + // this gets executed from the context of a HystrixCommand parent thread (such as a Tomcat thread) + // so we create the callable now where we can capture the thread context + callableWithContextOfParent = new HystrixContextCallable(concurrencyStrategy, new Callable() { + // the wrapCallable call allows a strategy to capture thread-context if desired + + @Override + public Void call() throws Exception { + try { + // we fetch current so that when multiple threads race + // we can do compareAndSet with the expected/new to ensure only one happens + RequestBatch currentBatch = batch.get(); + // 1) it can be null if it got shutdown + // 2) we don't execute this batch if it has no requests and let it wait until next tick to be executed + if (currentBatch != null && currentBatch.getSize() > 0) { + // do execution within context of wrapped Callable + createNewBatchAndExecutePreviousIfNeeded(currentBatch); + } + } catch (Throwable t) { + logger.error("Error occurred trying to execute the batch.", t); + t.printStackTrace(); + // ignore error so we don't kill the Timer mainLoop and prevent further items from being scheduled + } + return null; + } + + }); + } + + @Override + public void tick() { + try { + callableWithContextOfParent.call(); + } catch (Exception e) { + logger.error("Error occurred trying to execute callable inside CollapsedTask from Timer.", e); + e.printStackTrace(); + } + } + + @Override + public int getIntervalTimeInMilliseconds() { + return properties.timerDelayInMilliseconds().get(); + } + + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapserFactory.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapserFactory.java new file mode 100644 index 0000000..fcffa35 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapserFactory.java @@ -0,0 +1,219 @@ +/** + * 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.collapser; + +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableHolder; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; +import com.netflix.hystrix.util.HystrixTimer; + +/** + * Factory for retrieving the correct instance of a RequestCollapser. + * + * @param + * @param + * @param + */ +public class RequestCollapserFactory { + + private static final Logger logger = LoggerFactory.getLogger(RequestCollapserFactory.class); + + private final CollapserTimer timer; + private final HystrixCollapserKey collapserKey; + private final HystrixCollapserProperties properties; + private final HystrixConcurrencyStrategy concurrencyStrategy; + private final Scope scope; + + public static interface Scope { + String name(); + } + + // internally expected scopes, dealing with the not-so-fun inheritance issues of enum when shared between classes + private static enum Scopes implements Scope { + REQUEST, GLOBAL + } + + public RequestCollapserFactory(HystrixCollapserKey collapserKey, Scope scope, CollapserTimer timer, HystrixCollapserProperties.Setter propertiesBuilder) { + this(collapserKey, scope, timer, HystrixPropertiesFactory.getCollapserProperties(collapserKey, propertiesBuilder)); + } + + public RequestCollapserFactory(HystrixCollapserKey collapserKey, Scope scope, CollapserTimer timer, HystrixCollapserProperties properties) { + /* strategy: ConcurrencyStrategy */ + this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy(); + this.timer = timer; + this.scope = scope; + this.collapserKey = collapserKey; + this.properties = properties; + + } + + public HystrixCollapserKey getCollapserKey() { + return collapserKey; + } + + public Scope getScope() { + return scope; + } + + public HystrixCollapserProperties getProperties() { + return properties; + } + + public RequestCollapser getRequestCollapser(HystrixCollapserBridge commandCollapser) { + if (Scopes.REQUEST == Scopes.valueOf(getScope().name())) { + return getCollapserForUserRequest(commandCollapser); + } else if (Scopes.GLOBAL == Scopes.valueOf(getScope().name())) { + return getCollapserForGlobalScope(commandCollapser); + } else { + logger.warn("Invalid Scope: {} Defaulting to REQUEST scope.", getScope()); + return getCollapserForUserRequest(commandCollapser); + } + } + + /** + * Static global cache of RequestCollapsers for Scope.GLOBAL + */ + // String is CollapserKey.name() (we can't use CollapserKey directly as we can't guarantee it implements hashcode/equals correctly) + private static ConcurrentHashMap> globalScopedCollapsers = new ConcurrentHashMap>(); + + @SuppressWarnings("unchecked") + private RequestCollapser getCollapserForGlobalScope(HystrixCollapserBridge commandCollapser) { + RequestCollapser collapser = globalScopedCollapsers.get(collapserKey.name()); + if (collapser != null) { + return (RequestCollapser) collapser; + } + // create new collapser using 'this' first instance as the one that will get cached for future executions ('this' is stateless so we can do that) + RequestCollapser newCollapser = new RequestCollapser(commandCollapser, properties, timer, concurrencyStrategy); + RequestCollapser existing = globalScopedCollapsers.putIfAbsent(collapserKey.name(), newCollapser); + if (existing == null) { + // we won + return newCollapser; + } else { + // we lost ... another thread beat us + // shutdown the one we created but didn't get stored + newCollapser.shutdown(); + // return the existing one + return (RequestCollapser) existing; + } + } + + /** + * Static global cache of RequestVariables with RequestCollapsers for Scope.REQUEST + */ + // String is HystrixCollapserKey.name() (we can't use HystrixCollapserKey directly as we can't guarantee it implements hashcode/equals correctly) + private static ConcurrentHashMap>> requestScopedCollapsers = new ConcurrentHashMap>>(); + + /* we are casting because the Map needs to be but we know it is for this thread */ + @SuppressWarnings("unchecked") + private RequestCollapser getCollapserForUserRequest(HystrixCollapserBridge commandCollapser) { + return (RequestCollapser) getRequestVariableForCommand(commandCollapser).get(concurrencyStrategy); + } + + /** + * Lookup (or create and store) the RequestVariable for a given HystrixCollapserKey. + * + * @param commandCollapser collapser to retrieve {@link HystrixRequestVariableHolder} for + * @return HystrixRequestVariableHolder + */ + @SuppressWarnings("unchecked") + private HystrixRequestVariableHolder> getRequestVariableForCommand(final HystrixCollapserBridge commandCollapser) { + HystrixRequestVariableHolder> requestVariable = requestScopedCollapsers.get(commandCollapser.getCollapserKey().name()); + if (requestVariable == null) { + // create new collapser using 'this' first instance as the one that will get cached for future executions ('this' is stateless so we can do that) + @SuppressWarnings({ "rawtypes" }) + HystrixRequestVariableHolder newCollapser = new RequestCollapserRequestVariable(commandCollapser, properties, timer, concurrencyStrategy); + HystrixRequestVariableHolder> existing = requestScopedCollapsers.putIfAbsent(commandCollapser.getCollapserKey().name(), newCollapser); + if (existing == null) { + // this thread won, so return the one we just created + requestVariable = newCollapser; + } else { + // another thread beat us (this should only happen when we have concurrency on the FIRST request for the life of the app for this HystrixCollapser class) + requestVariable = existing; + /* + * This *should* be okay to discard the created object without cleanup as the RequestVariable implementation + * should properly do lazy-initialization and only call initialValue() the first time get() is called. + * + * If it does not correctly follow this contract then there is a chance of a memory leak here. + */ + } + } + return requestVariable; + } + + /** + * Clears all state. If new requests come in instances will be recreated and metrics started from scratch. + */ + public static void reset() { + globalScopedCollapsers.clear(); + requestScopedCollapsers.clear(); + HystrixTimer.reset(); + } + + /** + * Used for testing + */ + public static void resetRequest() { + requestScopedCollapsers.clear(); + } + + /** + * Used for testing + */ + public static HystrixRequestVariableHolder> getRequestVariable(String key) { + return requestScopedCollapsers.get(key); + } + + /** + * Request scoped RequestCollapser that lives inside a RequestVariable. + *

+ * This depends on the RequestVariable getting reset before each user request in NFFilter to ensure the RequestCollapser is new for each user request. + */ + private final class RequestCollapserRequestVariable extends HystrixRequestVariableHolder> { + + /** + * NOTE: There is only 1 instance of this for the life of the app per HystrixCollapser instance. The state changes on each request via the initialValue()/get() methods. + *

+ * Thus, do NOT put any instance variables in this class that are not static for all threads. + */ + + private RequestCollapserRequestVariable(final HystrixCollapserBridge commandCollapser, final HystrixCollapserProperties properties, final CollapserTimer timer, final HystrixConcurrencyStrategy concurrencyStrategy) { + super(new HystrixRequestVariableLifecycle>() { + @Override + public RequestCollapser initialValue() { + // this gets calls once per request per HystrixCollapser instance + return new RequestCollapser(commandCollapser, properties, timer, concurrencyStrategy); + } + + @Override + public void shutdown(RequestCollapser currentCollapser) { + // shut down the RequestCollapser (the internal timer tasks) + if (currentCollapser != null) { + currentCollapser.shutdown(); + } + } + }); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixCollapserConfiguration.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixCollapserConfiguration.java new file mode 100644 index 0000000..d1eac15 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixCollapserConfiguration.java @@ -0,0 +1,111 @@ +/** + * Copyright 2016 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.config; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; + +public class HystrixCollapserConfiguration { + private final HystrixCollapserKey collapserKey; + private final int maxRequestsInBatch; + private final int timerDelayInMilliseconds; + private final boolean requestCacheEnabled; + private final CollapserMetricsConfig collapserMetricsConfig; + + public HystrixCollapserConfiguration(HystrixCollapserKey collapserKey, int maxRequestsInBatch, int timerDelayInMilliseconds, + boolean requestCacheEnabled, CollapserMetricsConfig collapserMetricsConfig) { + this.collapserKey = collapserKey; + this.maxRequestsInBatch = maxRequestsInBatch; + this.timerDelayInMilliseconds = timerDelayInMilliseconds; + this.requestCacheEnabled = requestCacheEnabled; + this.collapserMetricsConfig = collapserMetricsConfig; + } + + public static HystrixCollapserConfiguration sample(HystrixCollapserKey collapserKey, HystrixCollapserProperties collapserProperties) { + CollapserMetricsConfig collapserMetricsConfig = new CollapserMetricsConfig( + collapserProperties.metricsRollingPercentileWindowBuckets().get(), + collapserProperties.metricsRollingPercentileWindowInMilliseconds().get(), + collapserProperties.metricsRollingPercentileEnabled().get(), + collapserProperties.metricsRollingStatisticalWindowBuckets().get(), + collapserProperties.metricsRollingStatisticalWindowInMilliseconds().get() + ); + + return new HystrixCollapserConfiguration( + collapserKey, + collapserProperties.maxRequestsInBatch().get(), + collapserProperties.timerDelayInMilliseconds().get(), + collapserProperties.requestCacheEnabled().get(), + collapserMetricsConfig + ); + } + + public HystrixCollapserKey getCollapserKey() { + return collapserKey; + } + + public int getMaxRequestsInBatch() { + return maxRequestsInBatch; + } + + public int getTimerDelayInMilliseconds() { + return timerDelayInMilliseconds; + } + + public boolean isRequestCacheEnabled() { + return requestCacheEnabled; + } + + public CollapserMetricsConfig getCollapserMetricsConfig() { + return collapserMetricsConfig; + } + + public static class CollapserMetricsConfig { + private final int rollingPercentileNumberOfBuckets; + private final int rollingPercentileBucketSizeInMilliseconds; + private final boolean rollingPercentileEnabled; + private final int rollingCounterNumberOfBuckets; + private final int rollingCounterBucketSizeInMilliseconds; + + public CollapserMetricsConfig(int rollingPercentileNumberOfBuckets, int rollingPercentileBucketSizeInMilliseconds, boolean rollingPercentileEnabled, + int rollingCounterNumberOfBuckets, int rollingCounterBucketSizeInMilliseconds) { + this.rollingPercentileNumberOfBuckets = rollingCounterNumberOfBuckets; + this.rollingPercentileBucketSizeInMilliseconds = rollingPercentileBucketSizeInMilliseconds; + this.rollingPercentileEnabled = rollingPercentileEnabled; + this.rollingCounterNumberOfBuckets = rollingCounterNumberOfBuckets; + this.rollingCounterBucketSizeInMilliseconds = rollingCounterBucketSizeInMilliseconds; + } + + public int getRollingPercentileNumberOfBuckets() { + return rollingPercentileNumberOfBuckets; + } + + public int getRollingPercentileBucketSizeInMilliseconds() { + return rollingPercentileBucketSizeInMilliseconds; + } + + public boolean isRollingPercentileEnabled() { + return rollingPercentileEnabled; + } + + public int getRollingCounterNumberOfBuckets() { + return rollingCounterNumberOfBuckets; + } + + public int getRollingCounterBucketSizeInMilliseconds() { + return rollingCounterBucketSizeInMilliseconds; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixCommandConfiguration.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixCommandConfiguration.java new file mode 100644 index 0000000..20c3b2c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixCommandConfiguration.java @@ -0,0 +1,258 @@ +/** + * Copyright 2016 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.config; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPoolKey; + +public class HystrixCommandConfiguration { + //The idea is for this object to be serialized off-box. For future-proofing, I'm adding a version so that changing config over time can be handled gracefully + private static final String VERSION = "1"; + private final HystrixCommandKey commandKey; + private final HystrixThreadPoolKey threadPoolKey; + private final HystrixCommandGroupKey groupKey; + private final HystrixCommandExecutionConfig executionConfig; + private final HystrixCommandCircuitBreakerConfig circuitBreakerConfig; + private final HystrixCommandMetricsConfig metricsConfig; + + public HystrixCommandConfiguration(HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey, HystrixCommandGroupKey groupKey, + HystrixCommandExecutionConfig executionConfig, + HystrixCommandCircuitBreakerConfig circuitBreakerConfig, + HystrixCommandMetricsConfig metricsConfig) { + this.commandKey = commandKey; + this.threadPoolKey = threadPoolKey; + this.groupKey = groupKey; + this.executionConfig = executionConfig; + this.circuitBreakerConfig = circuitBreakerConfig; + this.metricsConfig = metricsConfig; + } + + public static HystrixCommandConfiguration sample(HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey, + HystrixCommandGroupKey groupKey, HystrixCommandProperties commandProperties) { + HystrixCommandExecutionConfig executionConfig = new HystrixCommandExecutionConfig( + commandProperties.executionIsolationSemaphoreMaxConcurrentRequests().get(), + commandProperties.executionIsolationStrategy().get(), + commandProperties.executionIsolationThreadInterruptOnTimeout().get(), + commandProperties.executionIsolationThreadPoolKeyOverride().get(), + commandProperties.executionTimeoutEnabled().get(), + commandProperties.executionTimeoutInMilliseconds().get(), + commandProperties.fallbackEnabled().get(), + commandProperties.fallbackIsolationSemaphoreMaxConcurrentRequests().get(), + commandProperties.requestCacheEnabled().get(), + commandProperties.requestLogEnabled().get() + ); + + HystrixCommandCircuitBreakerConfig circuitBreakerConfig = new HystrixCommandCircuitBreakerConfig( + commandProperties.circuitBreakerEnabled().get(), + commandProperties.circuitBreakerErrorThresholdPercentage().get(), + commandProperties.circuitBreakerForceClosed().get(), + commandProperties.circuitBreakerForceOpen().get(), + commandProperties.circuitBreakerRequestVolumeThreshold().get(), + commandProperties.circuitBreakerSleepWindowInMilliseconds().get() + ); + + HystrixCommandMetricsConfig metricsConfig = new HystrixCommandMetricsConfig( + commandProperties.metricsHealthSnapshotIntervalInMilliseconds().get(), + commandProperties.metricsRollingPercentileEnabled().get(), + commandProperties.metricsRollingPercentileWindowBuckets().get(), + commandProperties.metricsRollingPercentileWindowInMilliseconds().get(), + commandProperties.metricsRollingStatisticalWindowBuckets().get(), + commandProperties.metricsRollingStatisticalWindowInMilliseconds().get() + ); + + return new HystrixCommandConfiguration( + commandKey, threadPoolKey, groupKey, executionConfig, circuitBreakerConfig, metricsConfig); + } + + public HystrixThreadPoolKey getThreadPoolKey() { + return threadPoolKey; + } + + public HystrixCommandGroupKey getGroupKey() { + return groupKey; + } + + public HystrixCommandExecutionConfig getExecutionConfig() { + return executionConfig; + } + + public HystrixCommandCircuitBreakerConfig getCircuitBreakerConfig() { + return circuitBreakerConfig; + } + + public HystrixCommandMetricsConfig getMetricsConfig() { + return metricsConfig; + } + + public static class HystrixCommandCircuitBreakerConfig { + private final boolean enabled; + private final int errorThresholdPercentage; + private final boolean forceClosed; + private final boolean forceOpen; + private final int requestVolumeThreshold; + private final int sleepWindowInMilliseconds; + + public HystrixCommandCircuitBreakerConfig(boolean enabled, int errorThresholdPercentage, boolean forceClosed, + boolean forceOpen, int requestVolumeThreshold, int sleepWindowInMilliseconds) { + this.enabled = enabled; + this.errorThresholdPercentage = errorThresholdPercentage; + this.forceClosed = forceClosed; + this.forceOpen = forceOpen; + this.requestVolumeThreshold = requestVolumeThreshold; + this.sleepWindowInMilliseconds = sleepWindowInMilliseconds; + } + + public boolean isEnabled() { + return enabled; + } + + public int getErrorThresholdPercentage() { + return errorThresholdPercentage; + } + + public boolean isForceClosed() { + return forceClosed; + } + + public boolean isForceOpen() { + return forceOpen; + } + + public int getRequestVolumeThreshold() { + return requestVolumeThreshold; + } + + public int getSleepWindowInMilliseconds() { + return sleepWindowInMilliseconds; + } + } + + public static class HystrixCommandExecutionConfig { + private final int semaphoreMaxConcurrentRequests; + private final HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy; + private final boolean threadInterruptOnTimeout; + private final String threadPoolKeyOverride; + private final boolean timeoutEnabled; + private final int timeoutInMilliseconds; + private final boolean fallbackEnabled; + private final int fallbackMaxConcurrentRequest; + private final boolean requestCacheEnabled; + private final boolean requestLogEnabled; + + public HystrixCommandExecutionConfig(int semaphoreMaxConcurrentRequests, HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy, + boolean threadInterruptOnTimeout, String threadPoolKeyOverride, boolean timeoutEnabled, + int timeoutInMilliseconds, boolean fallbackEnabled, int fallbackMaxConcurrentRequests, + boolean requestCacheEnabled, boolean requestLogEnabled) { + this.semaphoreMaxConcurrentRequests = semaphoreMaxConcurrentRequests; + this.isolationStrategy = isolationStrategy; + this.threadInterruptOnTimeout = threadInterruptOnTimeout; + this.threadPoolKeyOverride = threadPoolKeyOverride; + this.timeoutEnabled = timeoutEnabled; + this.timeoutInMilliseconds = timeoutInMilliseconds; + this.fallbackEnabled = fallbackEnabled; + this.fallbackMaxConcurrentRequest = fallbackMaxConcurrentRequests; + this.requestCacheEnabled = requestCacheEnabled; + this.requestLogEnabled = requestLogEnabled; + + } + + public int getSemaphoreMaxConcurrentRequests() { + return semaphoreMaxConcurrentRequests; + } + + public HystrixCommandProperties.ExecutionIsolationStrategy getIsolationStrategy() { + return isolationStrategy; + } + + public boolean isThreadInterruptOnTimeout() { + return threadInterruptOnTimeout; + } + + public String getThreadPoolKeyOverride() { + return threadPoolKeyOverride; + } + + public boolean isTimeoutEnabled() { + return timeoutEnabled; + } + + public int getTimeoutInMilliseconds() { + return timeoutInMilliseconds; + } + + public boolean isFallbackEnabled() { + return fallbackEnabled; + } + + public int getFallbackMaxConcurrentRequest() { + return fallbackMaxConcurrentRequest; + } + + public boolean isRequestCacheEnabled() { + return requestCacheEnabled; + } + + public boolean isRequestLogEnabled() { + return requestLogEnabled; + } + } + + public static class HystrixCommandMetricsConfig { + private final int healthIntervalInMilliseconds; + private final boolean rollingPercentileEnabled; + private final int rollingPercentileNumberOfBuckets; + private final int rollingPercentileBucketSizeInMilliseconds; + private final int rollingCounterNumberOfBuckets; + private final int rollingCounterBucketSizeInMilliseconds; + + public HystrixCommandMetricsConfig(int healthIntervalInMilliseconds, boolean rollingPercentileEnabled, int rollingPercentileNumberOfBuckets, + int rollingPercentileBucketSizeInMilliseconds, int rollingCounterNumberOfBuckets, + int rollingCounterBucketSizeInMilliseconds) { + this.healthIntervalInMilliseconds = healthIntervalInMilliseconds; + this.rollingPercentileEnabled = rollingPercentileEnabled; + this.rollingPercentileNumberOfBuckets = rollingPercentileNumberOfBuckets; + this.rollingPercentileBucketSizeInMilliseconds = rollingPercentileBucketSizeInMilliseconds; + this.rollingCounterNumberOfBuckets = rollingCounterNumberOfBuckets; + this.rollingCounterBucketSizeInMilliseconds = rollingCounterBucketSizeInMilliseconds; + } + + public int getHealthIntervalInMilliseconds() { + return healthIntervalInMilliseconds; + } + + public boolean isRollingPercentileEnabled() { + return rollingPercentileEnabled; + } + + public int getRollingPercentileNumberOfBuckets() { + return rollingPercentileNumberOfBuckets; + } + + public int getRollingPercentileBucketSizeInMilliseconds() { + return rollingPercentileBucketSizeInMilliseconds; + } + + public int getRollingCounterNumberOfBuckets() { + return rollingCounterNumberOfBuckets; + } + + public int getRollingCounterBucketSizeInMilliseconds() { + return rollingCounterBucketSizeInMilliseconds; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixConfiguration.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixConfiguration.java new file mode 100644 index 0000000..f2d101e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixConfiguration.java @@ -0,0 +1,54 @@ +/** + * Copyright 2016 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.config; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixThreadPoolKey; + +import java.util.Map; + +public class HystrixConfiguration { + private final Map commandConfig; + private final Map threadPoolConfig; + private final Map collapserConfig; + + public HystrixConfiguration(Map commandConfig, + Map threadPoolConfig, + Map collapserConfig) { + this.commandConfig = commandConfig; + this.threadPoolConfig = threadPoolConfig; + this.collapserConfig = collapserConfig; + } + + public static HystrixConfiguration from(Map commandConfig, + Map threadPoolConfig, + Map collapserConfig) { + return new HystrixConfiguration(commandConfig, threadPoolConfig, collapserConfig); + } + + public Map getCommandConfig() { + return commandConfig; + } + + public Map getThreadPoolConfig() { + return threadPoolConfig; + } + + public Map getCollapserConfig() { + return collapserConfig; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixConfigurationStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixConfigurationStream.java new file mode 100644 index 0000000..2d83e36 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixConfigurationStream.java @@ -0,0 +1,207 @@ +/** + * Copyright 2016 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.config; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Func1; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class samples current Hystrix configuration and exposes that as a stream + */ +public class HystrixConfigurationStream { + + private final int intervalInMilliseconds; + private final Observable allConfigurationStream; + private final AtomicBoolean isSourceCurrentlySubscribed = new AtomicBoolean(false); + + private static final DynamicIntProperty dataEmissionIntervalInMs = + DynamicPropertyFactory.getInstance().getIntProperty("hystrix.stream.config.intervalInMilliseconds", 5000); + + + private static final Func1 getAllConfig = + new Func1() { + @Override + public HystrixConfiguration call(Long timestamp) { + return HystrixConfiguration.from( + getAllCommandConfig.call(timestamp), + getAllThreadPoolConfig.call(timestamp), + getAllCollapserConfig.call(timestamp) + ); + } + }; + + /** + * @deprecated Not for public use. Please use {@link #getInstance()}. This facilitates better stream-sharing + * @param intervalInMilliseconds milliseconds between data emissions + */ + @Deprecated //deprecated in 1.5.4. + public HystrixConfigurationStream(final int intervalInMilliseconds) { + this.intervalInMilliseconds = intervalInMilliseconds; + this.allConfigurationStream = Observable.interval(intervalInMilliseconds, TimeUnit.MILLISECONDS) + .map(getAllConfig) + .doOnSubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(true); + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(false); + } + }) + .share() + .onBackpressureDrop(); + } + + //The data emission interval is looked up on startup only + private static final HystrixConfigurationStream INSTANCE = + new HystrixConfigurationStream(dataEmissionIntervalInMs.get()); + + public static HystrixConfigurationStream getInstance() { + return INSTANCE; + } + + static HystrixConfigurationStream getNonSingletonInstanceOnlyUsedInUnitTests(int delayInMs) { + return new HystrixConfigurationStream(delayInMs); + } + + /** + * Return a ref-counted stream that will only do work when at least one subscriber is present + */ + public Observable observe() { + return allConfigurationStream; + } + + public Observable> observeCommandConfiguration() { + return allConfigurationStream.map(getOnlyCommandConfig); + } + + public Observable> observeThreadPoolConfiguration() { + return allConfigurationStream.map(getOnlyThreadPoolConfig); + } + + public Observable> observeCollapserConfiguration() { + return allConfigurationStream.map(getOnlyCollapserConfig); + } + + public int getIntervalInMilliseconds() { + return this.intervalInMilliseconds; + } + + public boolean isSourceCurrentlySubscribed() { + return isSourceCurrentlySubscribed.get(); + } + + private static HystrixCommandConfiguration sampleCommandConfiguration(HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey, + HystrixCommandGroupKey groupKey, HystrixCommandProperties commandProperties) { + return HystrixCommandConfiguration.sample(commandKey, threadPoolKey, groupKey, commandProperties); + } + + private static HystrixThreadPoolConfiguration sampleThreadPoolConfiguration(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { + return HystrixThreadPoolConfiguration.sample(threadPoolKey, threadPoolProperties); + } + + private static HystrixCollapserConfiguration sampleCollapserConfiguration(HystrixCollapserKey collapserKey, HystrixCollapserProperties collapserProperties) { + return HystrixCollapserConfiguration.sample(collapserKey, collapserProperties); + } + + private static final Func1> getAllCommandConfig = + new Func1>() { + @Override + public Map call(Long timestamp) { + Map commandConfigPerKey = new HashMap(); + for (HystrixCommandMetrics commandMetrics: HystrixCommandMetrics.getInstances()) { + HystrixCommandKey commandKey = commandMetrics.getCommandKey(); + HystrixThreadPoolKey threadPoolKey = commandMetrics.getThreadPoolKey(); + HystrixCommandGroupKey groupKey = commandMetrics.getCommandGroup(); + commandConfigPerKey.put(commandKey, sampleCommandConfiguration(commandKey, threadPoolKey, groupKey, commandMetrics.getProperties())); + } + return commandConfigPerKey; + } + }; + + private static final Func1> getAllThreadPoolConfig = + new Func1>() { + @Override + public Map call(Long timestamp) { + Map threadPoolConfigPerKey = new HashMap(); + for (HystrixThreadPoolMetrics threadPoolMetrics: HystrixThreadPoolMetrics.getInstances()) { + HystrixThreadPoolKey threadPoolKey = threadPoolMetrics.getThreadPoolKey(); + threadPoolConfigPerKey.put(threadPoolKey, sampleThreadPoolConfiguration(threadPoolKey, threadPoolMetrics.getProperties())); + } + return threadPoolConfigPerKey; + } + }; + + private static final Func1> getAllCollapserConfig = + new Func1>() { + @Override + public Map call(Long timestamp) { + Map collapserConfigPerKey = new HashMap(); + for (HystrixCollapserMetrics collapserMetrics: HystrixCollapserMetrics.getInstances()) { + HystrixCollapserKey collapserKey = collapserMetrics.getCollapserKey(); + collapserConfigPerKey.put(collapserKey, sampleCollapserConfiguration(collapserKey, collapserMetrics.getProperties())); + } + return collapserConfigPerKey; + } + }; + + + + private static final Func1> getOnlyCommandConfig = + new Func1>() { + @Override + public Map call(HystrixConfiguration hystrixConfiguration) { + return hystrixConfiguration.getCommandConfig(); + } + }; + + private static final Func1> getOnlyThreadPoolConfig = + new Func1>() { + @Override + public Map call(HystrixConfiguration hystrixConfiguration) { + return hystrixConfiguration.getThreadPoolConfig(); + } + }; + + private static final Func1> getOnlyCollapserConfig = + new Func1>() { + @Override + public Map call(HystrixConfiguration hystrixConfiguration) { + return hystrixConfiguration.getCollapserConfig(); + } + }; +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixThreadPoolConfiguration.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixThreadPoolConfiguration.java new file mode 100644 index 0000000..2c2fbee --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/config/HystrixThreadPoolConfiguration.java @@ -0,0 +1,113 @@ +/** + * Copyright 2016 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.config; + +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; + +public class HystrixThreadPoolConfiguration { + private final HystrixThreadPoolKey threadPoolKey; + private final int coreSize; + private final int maximumSize; + private final int actualMaximumSize; + private final int maxQueueSize; + private final int queueRejectionThreshold; + private final int keepAliveTimeInMinutes; + private final boolean allowMaximumSizeToDivergeFromCoreSize; + private final int rollingCounterNumberOfBuckets; + private final int rollingCounterBucketSizeInMilliseconds; + + private HystrixThreadPoolConfiguration(HystrixThreadPoolKey threadPoolKey, int coreSize, int maximumSize, int actualMaximumSize, int maxQueueSize, int queueRejectionThreshold, + int keepAliveTimeInMinutes, boolean allowMaximumSizeToDivergeFromCoreSize, int rollingCounterNumberOfBuckets, + int rollingCounterBucketSizeInMilliseconds) { + this.threadPoolKey = threadPoolKey; + this.allowMaximumSizeToDivergeFromCoreSize = allowMaximumSizeToDivergeFromCoreSize; + this.coreSize = coreSize; + this.maximumSize = maximumSize; + this.actualMaximumSize = actualMaximumSize; + this.maxQueueSize = maxQueueSize; + this.queueRejectionThreshold = queueRejectionThreshold; + this.keepAliveTimeInMinutes = keepAliveTimeInMinutes; + this.rollingCounterNumberOfBuckets = rollingCounterNumberOfBuckets; + this.rollingCounterBucketSizeInMilliseconds = rollingCounterBucketSizeInMilliseconds; + } + + private HystrixThreadPoolConfiguration(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { + this(threadPoolKey, threadPoolProperties.coreSize().get(), + threadPoolProperties.maximumSize().get(), threadPoolProperties.actualMaximumSize(), + threadPoolProperties.maxQueueSize().get(), threadPoolProperties.queueSizeRejectionThreshold().get(), + threadPoolProperties.keepAliveTimeMinutes().get(), threadPoolProperties.getAllowMaximumSizeToDivergeFromCoreSize().get(), + threadPoolProperties.metricsRollingStatisticalWindowBuckets().get(), + threadPoolProperties.metricsRollingStatisticalWindowInMilliseconds().get()); + } + + public static HystrixThreadPoolConfiguration sample(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { + return new HystrixThreadPoolConfiguration(threadPoolKey, threadPoolProperties); + } + + public HystrixThreadPoolKey getThreadPoolKey() { + return threadPoolKey; + } + + public int getCoreSize() { + return coreSize; + } + + /** + * Simple getter that returns what the `maximumSize` property is configured as + * @return + */ + public int getMaximumSize() { + return maximumSize; + } + + /** + * Given all of the thread pool configuration, what is the actual maximumSize applied to the thread pool. + * + * Cases: + * 1) allowMaximumSizeToDivergeFromCoreSize == false: maximumSize is set to coreSize + * 2) allowMaximumSizeToDivergeFromCoreSize == true, maximumSize >= coreSize: thread pool has different core/max sizes, so return the configured max + * 3) allowMaximumSizeToDivergeFromCoreSize == true, maximumSize < coreSize: threadpool incorrectly configured, use coreSize for max size + * @return actually configured maximum size of threadpool + */ + public int getActualMaximumSize() { + return this.actualMaximumSize; + } + + public int getMaxQueueSize() { + return maxQueueSize; + } + + public int getQueueRejectionThreshold() { + return queueRejectionThreshold; + } + + public int getKeepAliveTimeInMinutes() { + return keepAliveTimeInMinutes; + } + + public boolean getAllowMaximumSizeToDivergeFromCoreSize() { + return allowMaximumSizeToDivergeFromCoreSize; + } + + public int getRollingCounterNumberOfBuckets() { + return rollingCounterNumberOfBuckets; + } + + public int getRollingCounterBucketSizeInMilliseconds() { + return rollingCounterBucketSizeInMilliseconds; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/ExceptionNotWrappedByHystrix.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/ExceptionNotWrappedByHystrix.java new file mode 100644 index 0000000..e283a01 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/ExceptionNotWrappedByHystrix.java @@ -0,0 +1,8 @@ +package com.netflix.hystrix.exception; + +/** + * Exceptions can implement this interface to prevent Hystrix from wrapping detected exceptions in a HystrixRuntimeException + */ +public interface ExceptionNotWrappedByHystrix { + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixBadRequestException.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixBadRequestException.java new file mode 100644 index 0000000..52d262c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixBadRequestException.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012 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.exception; + +import com.netflix.hystrix.HystrixCommand; + +/** + * An exception representing an error with provided arguments or state rather than an execution failure. + *

+ * Unlike all other exceptions thrown by a {@link HystrixCommand} this will not trigger fallback, not count against failure metrics and thus not trigger the circuit breaker. + *

+ * NOTE: This should only be used when an error is due to user input such as {@link IllegalArgumentException} otherwise it defeats the purpose of fault-tolerance and fallback behavior. + */ +public class HystrixBadRequestException extends RuntimeException { + + private static final long serialVersionUID = -8341452103561805856L; + + public HystrixBadRequestException(String message) { + super(message); + } + + public HystrixBadRequestException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixRuntimeException.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixRuntimeException.java new file mode 100644 index 0000000..5d13658 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixRuntimeException.java @@ -0,0 +1,79 @@ +/** + * Copyright 2012 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.exception; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.util.ExceptionThreadingUtility; + +/** + * RuntimeException that is thrown when a {@link HystrixCommand} fails and does not have a fallback. + */ +@SuppressWarnings("rawtypes") +public class HystrixRuntimeException extends RuntimeException { + + private static final long serialVersionUID = 5219160375476046229L; + + private final Class commandClass; + private final Throwable fallbackException; + private final FailureType failureCause; + + public static enum FailureType { + BAD_REQUEST_EXCEPTION, COMMAND_EXCEPTION, TIMEOUT, SHORTCIRCUIT, REJECTED_THREAD_EXECUTION, REJECTED_SEMAPHORE_EXECUTION, REJECTED_SEMAPHORE_FALLBACK + } + + public HystrixRuntimeException(FailureType failureCause, Class commandClass, String message, Exception cause, Throwable fallbackException) { + super(message, cause); + this.failureCause = failureCause; + this.commandClass = commandClass; + this.fallbackException = fallbackException; + } + + public HystrixRuntimeException(FailureType failureCause, Class commandClass, String message, Throwable cause, Throwable fallbackException) { + super(message, cause); + this.failureCause = failureCause; + this.commandClass = commandClass; + this.fallbackException = fallbackException; + } + + /** + * The type of failure that caused this exception to be thrown. + * + * @return {@link FailureType} + */ + public FailureType getFailureType() { + return failureCause; + } + + /** + * The implementing class of the {@link HystrixCommand}. + * + * @return {@code Class } + */ + public Class getImplementingClass() { + return commandClass; + } + + /** + * The {@link Throwable} that was thrown when trying to retrieve a fallback. + * + * @return {@link Throwable} + */ + public Throwable getFallbackException() { + return fallbackException; + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixTimeoutException.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixTimeoutException.java new file mode 100644 index 0000000..dee41eb --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixTimeoutException.java @@ -0,0 +1,34 @@ +/** + * 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.exception; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixObservableCommand; + +/** + * An exception representing an error where the provided execution method took longer than the Hystrix timeout. + *

+ * Hystrix will trigger an exception of this type if it times out an execution. This class is public, so + * user-defined execution methods ({@link HystrixCommand#run()} / {@link HystrixObservableCommand#construct()) may also + * throw this error. If you want some types of failures to be counted as timeouts, you may wrap those failures in + * a HystrixTimeoutException. See https://github.com/Netflix/Hystrix/issues/920 for more discussion. + */ +public class HystrixTimeoutException extends Exception { + + private static final long serialVersionUID = -5085623652043595962L; + +} + diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/package-info.java new file mode 100644 index 0000000..9990b9b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/exception/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2012 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. + */ +/** + * Custom exception implementations. + * + * @since 1.0.0 + */ +package com.netflix.hystrix.exception; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/CachedValuesHistogram.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/CachedValuesHistogram.java new file mode 100644 index 0000000..b773977 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/CachedValuesHistogram.java @@ -0,0 +1,150 @@ +/** + * Copyright 2016 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.metric; + +import org.HdrHistogram.Histogram; + +public class CachedValuesHistogram { + + private final static int NUMBER_SIGNIFICANT_DIGITS = 3; + + private final int mean; + private final int p0; + private final int p5; + private final int p10; + private final int p15; + private final int p20; + private final int p25; + private final int p30; + private final int p35; + private final int p40; + private final int p45; + private final int p50; + private final int p55; + private final int p60; + private final int p65; + private final int p70; + private final int p75; + private final int p80; + private final int p85; + private final int p90; + private final int p95; + private final int p99; + private final int p99_5; + private final int p99_9; + private final int p99_95; + private final int p99_99; + private final int p100; + + private final long totalCount; + + public static CachedValuesHistogram backedBy(Histogram underlying) { + return new CachedValuesHistogram(underlying); + } + + private CachedValuesHistogram(Histogram underlying) { + /** + * Single thread calculates a variety of commonly-accessed quantities. + * This way, all threads can access the cached values without synchronization + * Synchronization is only required for values that are not cached + */ + + mean = (int) underlying.getMean(); + p0 = (int) underlying.getValueAtPercentile(0); + p5 = (int) underlying.getValueAtPercentile(5); + p10 = (int) underlying.getValueAtPercentile(10); + p15 = (int) underlying.getValueAtPercentile(15); + p20 = (int) underlying.getValueAtPercentile(20); + p25 = (int) underlying.getValueAtPercentile(25); + p30 = (int) underlying.getValueAtPercentile(30); + p35 = (int) underlying.getValueAtPercentile(35); + p40 = (int) underlying.getValueAtPercentile(40); + p45 = (int) underlying.getValueAtPercentile(45); + p50 = (int) underlying.getValueAtPercentile(50); + p55 = (int) underlying.getValueAtPercentile(55); + p60 = (int) underlying.getValueAtPercentile(60); + p65 = (int) underlying.getValueAtPercentile(65); + p70 = (int) underlying.getValueAtPercentile(70); + p75 = (int) underlying.getValueAtPercentile(75); + p80 = (int) underlying.getValueAtPercentile(80); + p85 = (int) underlying.getValueAtPercentile(85); + p90 = (int) underlying.getValueAtPercentile(90); + p95 = (int) underlying.getValueAtPercentile(95); + p99 = (int) underlying.getValueAtPercentile(99); + p99_5 = (int) underlying.getValueAtPercentile(99.5); + p99_9 = (int) underlying.getValueAtPercentile(99.9); + p99_95 = (int) underlying.getValueAtPercentile(99.95); + p99_99 = (int) underlying.getValueAtPercentile(99.99); + p100 = (int) underlying.getValueAtPercentile(100); + + totalCount = underlying.getTotalCount(); + } + + /** + * Return the cached value only + * @return cached distribution mean + */ + public int getMean() { + return mean; + } + + /** + * Return the cached value if available. + * Otherwise, we need to synchronize access to the underlying {@link Histogram} + * @param percentile percentile of distribution + * @return value at percentile (from cache if possible) + */ + public int getValueAtPercentile(double percentile) { + int permyriad = (int) (percentile * 100); + switch (permyriad) { + case 0: return p0; + case 500: return p5; + case 1000: return p10; + case 1500: return p15; + case 2000: return p20; + case 2500: return p25; + case 3000: return p30; + case 3500: return p35; + case 4000: return p40; + case 4500: return p45; + case 5000: return p50; + case 5500: return p55; + case 6000: return p60; + case 6500: return p65; + case 7000: return p70; + case 7500: return p75; + case 8000: return p80; + case 8500: return p85; + case 9000: return p90; + case 9500: return p95; + case 9900: return p99; + case 9950: return p99_5; + case 9990: return p99_9; + case 9995: return p99_95; + case 9999: return p99_99; + case 10000: return p100; + default: throw new IllegalArgumentException("Percentile (" + percentile + ") is not currently cached"); + } + } + + public long getTotalCount() { + return totalCount; + } + + public static Histogram getNewHistogram() { + return new Histogram(NUMBER_SIGNIFICANT_DIGITS); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCollapserEvent.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCollapserEvent.java new file mode 100644 index 0000000..3071c57 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCollapserEvent.java @@ -0,0 +1,62 @@ +/** + * 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.metric; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixEventType; + +/** + * Data class that comprises the event stream for Hystrix collapser executions. + * This class represents the 3 things that can happen (found in {@link com.netflix.hystrix.HystrixEventType.Collapser} enum: + *

    + *
  • ADDED_TO_BATCH
  • + *
  • BATCH_EXECUTED
  • + *
  • RESPONSE_FROM_CACHE
  • + *
+ * + */ +public class HystrixCollapserEvent implements HystrixEvent { + private final HystrixCollapserKey collapserKey; + private final HystrixEventType.Collapser eventType; + private final int count; + + protected HystrixCollapserEvent(HystrixCollapserKey collapserKey, HystrixEventType.Collapser eventType, int count) { + this.collapserKey = collapserKey; + this.eventType = eventType; + this.count = count; + } + + public static HystrixCollapserEvent from(HystrixCollapserKey collapserKey, HystrixEventType.Collapser eventType, int count) { + return new HystrixCollapserEvent(collapserKey, eventType, count); + } + + public HystrixCollapserKey getCollapserKey() { + return collapserKey; + } + + public HystrixEventType.Collapser getEventType() { + return eventType; + } + + public int getCount() { + return count; + } + + @Override + public String toString() { + return "HystrixCollapserEvent[" + collapserKey.name() + "] : " + eventType.name() + " : " + count; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCollapserEventStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCollapserEventStream.java new file mode 100644 index 0000000..4ae05b9 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCollapserEventStream.java @@ -0,0 +1,80 @@ +/** + * 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.metric; + +import com.netflix.hystrix.HystrixCollapserKey; +import rx.Observable; +import rx.subjects.PublishSubject; +import rx.subjects.SerializedSubject; +import rx.subjects.Subject; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Per-Collapser stream of {@link HystrixCollapserEvent}s. This gets written to by {@link HystrixThreadEventStream}s. + * Events are emitted synchronously in the same thread that performs the batch-command execution. + */ +public class HystrixCollapserEventStream implements HystrixEventStream { + private final HystrixCollapserKey collapserKey; + + private final Subject writeOnlyStream; + private final Observable readOnlyStream; + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + public static HystrixCollapserEventStream getInstance(HystrixCollapserKey collapserKey) { + HystrixCollapserEventStream initialStream = streams.get(collapserKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (HystrixCollapserEventStream.class) { + HystrixCollapserEventStream existingStream = streams.get(collapserKey.name()); + if (existingStream == null) { + HystrixCollapserEventStream newStream = new HystrixCollapserEventStream(collapserKey); + streams.putIfAbsent(collapserKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + HystrixCollapserEventStream(final HystrixCollapserKey collapserKey) { + this.collapserKey = collapserKey; + + this.writeOnlyStream = new SerializedSubject(PublishSubject.create()); + this.readOnlyStream = writeOnlyStream.share(); + } + + public static void reset() { + streams.clear(); + } + + public void write(HystrixCollapserEvent event) { + writeOnlyStream.onNext(event); + } + + public Observable observe() { + return readOnlyStream; + } + + @Override + public String toString() { + return "HystrixCollapserEventStream(" + collapserKey.name() + ")"; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandCompletion.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandCompletion.java new file mode 100644 index 0000000..c9ef959 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandCompletion.java @@ -0,0 +1,119 @@ +/** + * 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.metric; + +import com.netflix.hystrix.ExecutionResult; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * Data class which gets fed into event stream when a command completes (with any of the outcomes in {@link HystrixEventType}). + */ +public class HystrixCommandCompletion extends HystrixCommandEvent { + protected final ExecutionResult executionResult; + protected final HystrixRequestContext requestContext; + + private final static HystrixEventType[] ALL_EVENT_TYPES = HystrixEventType.values(); + + HystrixCommandCompletion(ExecutionResult executionResult, HystrixCommandKey commandKey, + HystrixThreadPoolKey threadPoolKey, HystrixRequestContext requestContext) { + super(commandKey, threadPoolKey); + this.executionResult = executionResult; + this.requestContext = requestContext; + } + + public static HystrixCommandCompletion from(ExecutionResult executionResult, HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey) { + return from(executionResult, commandKey, threadPoolKey, HystrixRequestContext.getContextForCurrentThread()); + } + + public static HystrixCommandCompletion from(ExecutionResult executionResult, HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey, HystrixRequestContext requestContext) { + return new HystrixCommandCompletion(executionResult, commandKey, threadPoolKey, requestContext); + } + + @Override + public boolean isResponseThreadPoolRejected() { + return executionResult.isResponseThreadPoolRejected(); + } + + @Override + public boolean isExecutionStart() { + return false; + } + + @Override + public boolean isExecutedInThread() { + return executionResult.isExecutedInThread(); + } + + @Override + public boolean isCommandCompletion() { + return true; + } + + public HystrixRequestContext getRequestContext() { + return this.requestContext; + } + + public ExecutionResult.EventCounts getEventCounts() { + return executionResult.getEventCounts(); + } + + public long getExecutionLatency() { + return executionResult.getExecutionLatency(); + } + + public long getTotalLatency() { + return executionResult.getUserThreadLatency(); + } + + @Override + public boolean didCommandExecute() { + return executionResult.executionOccurred(); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + List foundEventTypes = new ArrayList(); + + sb.append(getCommandKey().name()).append("["); + for (HystrixEventType eventType: ALL_EVENT_TYPES) { + if (executionResult.getEventCounts().contains(eventType)) { + foundEventTypes.add(eventType); + } + } + int i = 0; + for (HystrixEventType eventType: foundEventTypes) { + sb.append(eventType.name()); + int eventCount = executionResult.getEventCounts().getCount(eventType); + if (eventCount > 1) { + sb.append("x").append(eventCount); + + } + if (i < foundEventTypes.size() - 1) { + sb.append(", "); + } + i++; + } + sb.append("][").append(getExecutionLatency()).append(" ms]"); + return sb.toString(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandCompletionStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandCompletionStream.java new file mode 100644 index 0000000..5f9a1aa --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandCompletionStream.java @@ -0,0 +1,82 @@ +/** + * 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.metric; + +import com.netflix.hystrix.HystrixCommandKey; +import rx.Observable; +import rx.subjects.PublishSubject; +import rx.subjects.SerializedSubject; +import rx.subjects.Subject; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Per-Command stream of {@link HystrixCommandCompletion}s. This gets written to by {@link HystrixThreadEventStream}s. + * Events are emitted synchronously in the same thread that performs the command execution. + */ +public class HystrixCommandCompletionStream implements HystrixEventStream { + private final HystrixCommandKey commandKey; + + private final Subject writeOnlySubject; + private final Observable readOnlyStream; + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + public static HystrixCommandCompletionStream getInstance(HystrixCommandKey commandKey) { + HystrixCommandCompletionStream initialStream = streams.get(commandKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (HystrixCommandCompletionStream.class) { + HystrixCommandCompletionStream existingStream = streams.get(commandKey.name()); + if (existingStream == null) { + HystrixCommandCompletionStream newStream = new HystrixCommandCompletionStream(commandKey); + streams.putIfAbsent(commandKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + HystrixCommandCompletionStream(final HystrixCommandKey commandKey) { + this.commandKey = commandKey; + + this.writeOnlySubject = new SerializedSubject(PublishSubject.create()); + this.readOnlyStream = writeOnlySubject.share(); + } + + public static void reset() { + streams.clear(); + } + + public void write(HystrixCommandCompletion event) { + writeOnlySubject.onNext(event); + } + + + @Override + public Observable observe() { + return readOnlyStream; + } + + @Override + public String toString() { + return "HystrixCommandCompletionStream(" + commandKey.name() + ")"; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandEvent.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandEvent.java new file mode 100644 index 0000000..455747c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandEvent.java @@ -0,0 +1,66 @@ +/** + * 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.metric; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixThreadPoolKey; +import rx.functions.Func1; + +/** + * Data class that comprises the event stream for Hystrix command executions. + * As of 1.5.0-RC1, this is only {@link HystrixCommandCompletion}s. + */ +public abstract class HystrixCommandEvent implements HystrixEvent { + private final HystrixCommandKey commandKey; + private final HystrixThreadPoolKey threadPoolKey; + + protected HystrixCommandEvent(HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey) { + this.commandKey = commandKey; + this.threadPoolKey = threadPoolKey; + } + + public HystrixCommandKey getCommandKey() { + return commandKey; + } + + public HystrixThreadPoolKey getThreadPoolKey() { + return threadPoolKey; + } + + public abstract boolean isExecutionStart(); + + public abstract boolean isExecutedInThread(); + + public abstract boolean isResponseThreadPoolRejected(); + + public abstract boolean isCommandCompletion(); + + public abstract boolean didCommandExecute(); + + public static final Func1 filterCompletionsOnly = new Func1() { + @Override + public Boolean call(HystrixCommandEvent commandEvent) { + return commandEvent.isCommandCompletion(); + } + }; + + public static final Func1 filterActualExecutions = new Func1() { + @Override + public Boolean call(HystrixCommandEvent commandEvent) { + return commandEvent.didCommandExecute(); + } + }; +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandExecutionStarted.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandExecutionStarted.java new file mode 100644 index 0000000..5b92f3b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandExecutionStarted.java @@ -0,0 +1,66 @@ +/** + * 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.metric; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPoolKey; + +/** + * Data class that get fed to event stream when a command starts executing. + */ +public class HystrixCommandExecutionStarted extends HystrixCommandEvent { + private final HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy; + private final int currentConcurrency; + + public HystrixCommandExecutionStarted(HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey, + HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy, + int currentConcurrency) { + super(commandKey, threadPoolKey); + this.isolationStrategy = isolationStrategy; + this.currentConcurrency = currentConcurrency; + } + + @Override + public boolean isExecutionStart() { + return true; + } + + @Override + public boolean isExecutedInThread() { + return isolationStrategy == HystrixCommandProperties.ExecutionIsolationStrategy.THREAD; + } + + @Override + public boolean isResponseThreadPoolRejected() { + return false; + } + + @Override + public boolean isCommandCompletion() { + return false; + } + + @Override + public boolean didCommandExecute() { + return false; + } + + public int getCurrentConcurrency() { + return currentConcurrency; + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandStartStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandStartStream.java new file mode 100644 index 0000000..bd79404 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixCommandStartStream.java @@ -0,0 +1,81 @@ +/** + * 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.metric; + +import com.netflix.hystrix.HystrixCommandKey; +import rx.Observable; +import rx.subjects.PublishSubject; +import rx.subjects.SerializedSubject; +import rx.subjects.Subject; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Per-Command stream of {@link HystrixCommandExecutionStarted}s. This gets written to by {@link HystrixThreadEventStream}s. + * Events are emitted synchronously in the same thread that performs the command execution. + */ +public class HystrixCommandStartStream implements HystrixEventStream { + private final HystrixCommandKey commandKey; + + private final Subject writeOnlySubject; + private final Observable readOnlyStream; + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + public static HystrixCommandStartStream getInstance(HystrixCommandKey commandKey) { + HystrixCommandStartStream initialStream = streams.get(commandKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (HystrixCommandStartStream.class) { + HystrixCommandStartStream existingStream = streams.get(commandKey.name()); + if (existingStream == null) { + HystrixCommandStartStream newStream = new HystrixCommandStartStream(commandKey); + streams.putIfAbsent(commandKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + HystrixCommandStartStream(final HystrixCommandKey commandKey) { + this.commandKey = commandKey; + + this.writeOnlySubject = new SerializedSubject(PublishSubject.create()); + this.readOnlyStream = writeOnlySubject.share(); + } + + public static void reset() { + streams.clear(); + } + + public void write(HystrixCommandExecutionStarted event) { + writeOnlySubject.onNext(event); + } + + @Override + public Observable observe() { + return readOnlyStream; + } + + @Override + public String toString() { + return "HystrixCommandStartStream(" + commandKey.name() + ")"; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixEvent.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixEvent.java new file mode 100644 index 0000000..9dd3010 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixEvent.java @@ -0,0 +1,23 @@ +/** + * 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.metric; + +/** + * Marker interface for events which may appear in an event stream + */ +public interface HystrixEvent { + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixEventStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixEventStream.java new file mode 100644 index 0000000..a12836b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixEventStream.java @@ -0,0 +1,27 @@ +/** + * 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.metric; + +import rx.Observable; + +/** + * Base interface for a stream of {@link com.netflix.hystrix.HystrixEventType}s. Allows consumption by individual + * {@link com.netflix.hystrix.HystrixEventType} or by time-based bucketing of events + */ +public interface HystrixEventStream { + + Observable observe(); +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixRequestEvents.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixRequestEvents.java new file mode 100644 index 0000000..9eb7c6a --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixRequestEvents.java @@ -0,0 +1,194 @@ +/** + * Copyright 2016 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.metric; + +import com.netflix.hystrix.ExecutionResult; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixInvokableInfo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HystrixRequestEvents { + private final Collection> executions; + + public HystrixRequestEvents(Collection> executions) { + this.executions = executions; + } + + public Collection> getExecutions() { + return executions; + } + + public Map> getExecutionsMappedToLatencies() { + Map cachingDetector = new HashMap(); + List> nonCachedExecutions = new ArrayList>(executions.size()); + for (HystrixInvokableInfo execution: executions) { + if (execution.getPublicCacheKey() != null) { + //eligible for caching - might be the initial, or might be from cache + CommandAndCacheKey key = new CommandAndCacheKey(execution.getCommandKey().name(), execution.getPublicCacheKey()); + Integer count = cachingDetector.get(key); + if (count != null) { + //key already seen + cachingDetector.put(key, count + 1); + } else { + //key not seen yet + cachingDetector.put(key, 0); + } + } + if (!execution.isResponseFromCache()) { + nonCachedExecutions.add(execution); + } + } + + Map> commandDeduper = new HashMap>(); + for (HystrixInvokableInfo execution: nonCachedExecutions) { + int cachedCount = 0; + String cacheKey = execution.getPublicCacheKey(); + if (cacheKey != null) { + CommandAndCacheKey key = new CommandAndCacheKey(execution.getCommandKey().name(), cacheKey); + cachedCount = cachingDetector.get(key); + } + ExecutionSignature signature; + if (cachedCount > 0) { + //this has a RESPONSE_FROM_CACHE and needs to get split off + signature = ExecutionSignature.from(execution, cacheKey, cachedCount); + } else { + //nothing cached from this, can collapse further + signature = ExecutionSignature.from(execution); + } + List currentLatencyList = commandDeduper.get(signature); + if (currentLatencyList != null) { + currentLatencyList.add(execution.getExecutionTimeInMilliseconds()); + } else { + List newLatencyList = new ArrayList(); + newLatencyList.add(execution.getExecutionTimeInMilliseconds()); + commandDeduper.put(signature, newLatencyList); + } + } + + return commandDeduper; + } + + private static class CommandAndCacheKey { + private final String commandName; + private final String cacheKey; + + public CommandAndCacheKey(String commandName, String cacheKey) { + this.commandName = commandName; + this.cacheKey = cacheKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CommandAndCacheKey that = (CommandAndCacheKey) o; + + if (!commandName.equals(that.commandName)) return false; + return cacheKey.equals(that.cacheKey); + + } + + @Override + public int hashCode() { + int result = commandName.hashCode(); + result = 31 * result + cacheKey.hashCode(); + return result; + } + + @Override + public String toString() { + return "CommandAndCacheKey{" + + "commandName='" + commandName + '\'' + + ", cacheKey='" + cacheKey + '\'' + + '}'; + } + } + + public static class ExecutionSignature { + private final String commandName; + private final ExecutionResult.EventCounts eventCounts; + private final String cacheKey; + private final int cachedCount; + private final HystrixCollapserKey collapserKey; + private final int collapserBatchSize; + + private ExecutionSignature(HystrixCommandKey commandKey, ExecutionResult.EventCounts eventCounts, String cacheKey, int cachedCount, HystrixCollapserKey collapserKey, int collapserBatchSize) { + this.commandName = commandKey.name(); + this.eventCounts = eventCounts; + this.cacheKey = cacheKey; + this.cachedCount = cachedCount; + this.collapserKey = collapserKey; + this.collapserBatchSize = collapserBatchSize; + } + + public static ExecutionSignature from(HystrixInvokableInfo execution) { + return new ExecutionSignature(execution.getCommandKey(), execution.getEventCounts(), null, 0, execution.getOriginatingCollapserKey(), execution.getNumberCollapsed()); + } + + public static ExecutionSignature from(HystrixInvokableInfo execution, String cacheKey, int cachedCount) { + return new ExecutionSignature(execution.getCommandKey(), execution.getEventCounts(), cacheKey, cachedCount, execution.getOriginatingCollapserKey(), execution.getNumberCollapsed()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ExecutionSignature that = (ExecutionSignature) o; + + if (!commandName.equals(that.commandName)) return false; + if (!eventCounts.equals(that.eventCounts)) return false; + return !(cacheKey != null ? !cacheKey.equals(that.cacheKey) : that.cacheKey != null); + + } + + @Override + public int hashCode() { + int result = commandName.hashCode(); + result = 31 * result + eventCounts.hashCode(); + result = 31 * result + (cacheKey != null ? cacheKey.hashCode() : 0); + return result; + } + + public String getCommandName() { + return commandName; + } + + public ExecutionResult.EventCounts getEventCounts() { + return eventCounts; + } + + public int getCachedCount() { + return cachedCount; + } + + + public HystrixCollapserKey getCollapserKey() { + return collapserKey; + } + + public int getCollapserBatchSize() { + return collapserBatchSize; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixRequestEventsStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixRequestEventsStream.java new file mode 100644 index 0000000..29c7162 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixRequestEventsStream.java @@ -0,0 +1,55 @@ +/** + * Copyright 2016 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.metric; + +import com.netflix.hystrix.HystrixInvokableInfo; +import rx.Observable; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; + +import java.util.Collection; + +/** + * Stream of requests, each of which contains a series of command executions + */ +public class HystrixRequestEventsStream { + private final Subject writeOnlyRequestEventsSubject; + private final Observable readOnlyRequestEvents; + + /* package */ HystrixRequestEventsStream() { + writeOnlyRequestEventsSubject = PublishSubject.create(); + readOnlyRequestEvents = writeOnlyRequestEventsSubject.onBackpressureBuffer(1024); + } + + private static final HystrixRequestEventsStream INSTANCE = new HystrixRequestEventsStream(); + + public static HystrixRequestEventsStream getInstance() { + return INSTANCE; + } + + public void shutdown() { + writeOnlyRequestEventsSubject.onCompleted(); + } + + public void write(Collection> executions) { + HystrixRequestEvents requestEvents = new HystrixRequestEvents(executions); + writeOnlyRequestEventsSubject.onNext(requestEvents); + } + + public Observable observe() { + return readOnlyRequestEvents; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadEventStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadEventStream.java new file mode 100644 index 0000000..cfa8b80 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadEventStream.java @@ -0,0 +1,164 @@ +/** + * 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.metric; + +import com.netflix.hystrix.ExecutionResult; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import rx.functions.Action1; +import rx.observers.Subscribers; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; + +/** + * Per-thread event stream. No synchronization required when writing to it since it's single-threaded. + * + * Some threads will be dedicated to a single HystrixCommandKey (a member of a thread-isolated {@link HystrixThreadPool}. + * However, many situations arise where a single thread may serve many different commands. Examples include: + * * Application caller threads (semaphore-isolated commands, or thread-pool-rejections) + * * Timer threads (timeouts or collapsers) + *

+ * I don't think that a thread-level view is an interesting one to consume (I could be wrong), so at the moment there + * is no public way to consume it. I can always add it later, if desired. + *

+ * Instead, this stream writes to the following streams, which have more meaning to metrics consumers: + *

    + *
  • {@link HystrixCommandCompletionStream}
  • + *
  • {@link HystrixCommandStartStream}
  • + *
  • {@link HystrixThreadPoolCompletionStream}
  • + *
  • {@link HystrixThreadPoolStartStream}
  • + *
  • {@link HystrixCollapserEventStream}
  • + *
+ * + * Also note that any observers of this stream do so on the thread that writes the metric. This is the command caller + * thread in the SEMAPHORE-isolated case, and the Hystrix thread in the THREAD-isolated case. I determined this to + * be more efficient CPU-wise than immediately hopping off-thread and doing all the metric calculations in the + * RxComputationThreadPool. + */ +public class HystrixThreadEventStream { + private final long threadId; + private final String threadName; + + private final Subject writeOnlyCommandStartSubject; + private final Subject writeOnlyCommandCompletionSubject; + private final Subject writeOnlyCollapserSubject; + + private static final ThreadLocal threadLocalStreams = new ThreadLocal() { + @Override + protected HystrixThreadEventStream initialValue() { + return new HystrixThreadEventStream(Thread.currentThread()); + } + }; + + private static final Action1 writeCommandStartsToShardedStreams = new Action1() { + @Override + public void call(HystrixCommandExecutionStarted event) { + HystrixCommandStartStream commandStartStream = HystrixCommandStartStream.getInstance(event.getCommandKey()); + commandStartStream.write(event); + + if (event.isExecutedInThread()) { + HystrixThreadPoolStartStream threadPoolStartStream = HystrixThreadPoolStartStream.getInstance(event.getThreadPoolKey()); + threadPoolStartStream.write(event); + } + } + }; + + private static final Action1 writeCommandCompletionsToShardedStreams = new Action1() { + @Override + public void call(HystrixCommandCompletion commandCompletion) { + HystrixCommandCompletionStream commandStream = HystrixCommandCompletionStream.getInstance(commandCompletion.getCommandKey()); + commandStream.write(commandCompletion); + + if (commandCompletion.isExecutedInThread() || commandCompletion.isResponseThreadPoolRejected()) { + HystrixThreadPoolCompletionStream threadPoolStream = HystrixThreadPoolCompletionStream.getInstance(commandCompletion.getThreadPoolKey()); + threadPoolStream.write(commandCompletion); + } + } + }; + + private static final Action1 writeCollapserExecutionsToShardedStreams = new Action1() { + @Override + public void call(HystrixCollapserEvent collapserEvent) { + HystrixCollapserEventStream collapserStream = HystrixCollapserEventStream.getInstance(collapserEvent.getCollapserKey()); + collapserStream.write(collapserEvent); + } + }; + + /* package */ HystrixThreadEventStream(Thread thread) { + this.threadId = thread.getId(); + this.threadName = thread.getName(); + writeOnlyCommandStartSubject = PublishSubject.create(); + writeOnlyCommandCompletionSubject = PublishSubject.create(); + writeOnlyCollapserSubject = PublishSubject.create(); + + writeOnlyCommandStartSubject + .onBackpressureBuffer() + .doOnNext(writeCommandStartsToShardedStreams) + .unsafeSubscribe(Subscribers.empty()); + + writeOnlyCommandCompletionSubject + .onBackpressureBuffer() + .doOnNext(writeCommandCompletionsToShardedStreams) + .unsafeSubscribe(Subscribers.empty()); + + writeOnlyCollapserSubject + .onBackpressureBuffer() + .doOnNext(writeCollapserExecutionsToShardedStreams) + .unsafeSubscribe(Subscribers.empty()); + } + + public static HystrixThreadEventStream getInstance() { + return threadLocalStreams.get(); + } + + public void shutdown() { + writeOnlyCommandStartSubject.onCompleted(); + writeOnlyCommandCompletionSubject.onCompleted(); + writeOnlyCollapserSubject.onCompleted(); + } + + public void commandExecutionStarted(HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey, + HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy, int currentConcurrency) { + HystrixCommandExecutionStarted event = new HystrixCommandExecutionStarted(commandKey, threadPoolKey, isolationStrategy, currentConcurrency); + writeOnlyCommandStartSubject.onNext(event); + } + + public void executionDone(ExecutionResult executionResult, HystrixCommandKey commandKey, HystrixThreadPoolKey threadPoolKey) { + HystrixCommandCompletion event = HystrixCommandCompletion.from(executionResult, commandKey, threadPoolKey); + writeOnlyCommandCompletionSubject.onNext(event); + } + + public void collapserResponseFromCache(HystrixCollapserKey collapserKey) { + HystrixCollapserEvent collapserEvent = HystrixCollapserEvent.from(collapserKey, HystrixEventType.Collapser.RESPONSE_FROM_CACHE, 1); + writeOnlyCollapserSubject.onNext(collapserEvent); + } + + public void collapserBatchExecuted(HystrixCollapserKey collapserKey, int batchSize) { + HystrixCollapserEvent batchExecution = HystrixCollapserEvent.from(collapserKey, HystrixEventType.Collapser.BATCH_EXECUTED, 1); + HystrixCollapserEvent batchAdditions = HystrixCollapserEvent.from(collapserKey, HystrixEventType.Collapser.ADDED_TO_BATCH, batchSize); + writeOnlyCollapserSubject.onNext(batchExecution); + writeOnlyCollapserSubject.onNext(batchAdditions); + } + + @Override + public String toString() { + return "HystrixThreadEventStream (" + threadId + " - " + threadName + ")"; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadPoolCompletionStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadPoolCompletionStream.java new file mode 100644 index 0000000..f63ba62 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadPoolCompletionStream.java @@ -0,0 +1,82 @@ +/** + * 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.metric; + +import com.netflix.hystrix.HystrixThreadPoolKey; +import rx.Observable; +import rx.subjects.PublishSubject; +import rx.subjects.SerializedSubject; +import rx.subjects.Subject; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Per-ThreadPool stream of {@link HystrixCommandCompletion}s. This gets written to by {@link HystrixThreadEventStream}s. + * Events are emitted synchronously in the same thread that performs the command execution. + */ +public class HystrixThreadPoolCompletionStream implements HystrixEventStream { + + private final HystrixThreadPoolKey threadPoolKey; + + private final Subject writeOnlySubject; + private final Observable readOnlyStream; + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + public static HystrixThreadPoolCompletionStream getInstance(HystrixThreadPoolKey threadPoolKey) { + HystrixThreadPoolCompletionStream initialStream = streams.get(threadPoolKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (HystrixThreadPoolCompletionStream.class) { + HystrixThreadPoolCompletionStream existingStream = streams.get(threadPoolKey.name()); + if (existingStream == null) { + HystrixThreadPoolCompletionStream newStream = new HystrixThreadPoolCompletionStream(threadPoolKey); + streams.putIfAbsent(threadPoolKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + HystrixThreadPoolCompletionStream(final HystrixThreadPoolKey threadPoolKey) { + this.threadPoolKey = threadPoolKey; + + this.writeOnlySubject = new SerializedSubject(PublishSubject.create()); + this.readOnlyStream = writeOnlySubject.share(); + } + + public static void reset() { + streams.clear(); + } + + public void write(HystrixCommandCompletion event) { + writeOnlySubject.onNext(event); + } + + @Override + public Observable observe() { + return readOnlyStream; + } + + @Override + public String toString() { + return "HystrixThreadPoolCompletionStream(" + threadPoolKey.name() + ")"; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadPoolStartStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadPoolStartStream.java new file mode 100644 index 0000000..e517542 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/HystrixThreadPoolStartStream.java @@ -0,0 +1,82 @@ +/** + * Copyright 2016 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.metric; + +import com.netflix.hystrix.HystrixThreadPoolKey; +import rx.Observable; +import rx.subjects.PublishSubject; +import rx.subjects.SerializedSubject; +import rx.subjects.Subject; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Per-ThreadPool stream of {@link HystrixCommandExecutionStarted}s. This gets written to by {@link HystrixThreadEventStream}s. + * Events are emitted synchronously in the same thread that performs the command execution. + */ +public class HystrixThreadPoolStartStream implements HystrixEventStream { + + private final HystrixThreadPoolKey threadPoolKey; + + private final Subject writeOnlySubject; + private final Observable readOnlyStream; + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + public static HystrixThreadPoolStartStream getInstance(HystrixThreadPoolKey threadPoolKey) { + HystrixThreadPoolStartStream initialStream = streams.get(threadPoolKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (HystrixThreadPoolStartStream.class) { + HystrixThreadPoolStartStream existingStream = streams.get(threadPoolKey.name()); + if (existingStream == null) { + HystrixThreadPoolStartStream newStream = new HystrixThreadPoolStartStream(threadPoolKey); + streams.putIfAbsent(threadPoolKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + HystrixThreadPoolStartStream(final HystrixThreadPoolKey threadPoolKey) { + this.threadPoolKey = threadPoolKey; + + this.writeOnlySubject = new SerializedSubject(PublishSubject.create()); + this.readOnlyStream = writeOnlySubject.share(); + } + + public static void reset() { + streams.clear(); + } + + public void write(HystrixCommandExecutionStarted event) { + writeOnlySubject.onNext(event); + } + + @Override + public Observable observe() { + return readOnlyStream; + } + + @Override + public String toString() { + return "HystrixThreadPoolStartStream(" + threadPoolKey.name() + ")"; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedCounterStream.java new file mode 100644 index 0000000..5fb735d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedCounterStream.java @@ -0,0 +1,118 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.metric.HystrixEvent; +import com.netflix.hystrix.metric.HystrixEventStream; +import rx.Observable; +import rx.Subscription; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.subjects.BehaviorSubject; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Abstract class that imposes a bucketing structure and provides streams of buckets + * + * @param type of raw data that needs to get summarized into a bucket + * @param type of data contained in each bucket + * @param type of data emitted to stream subscribers (often is the same as A but does not have to be) + */ +public abstract class BucketedCounterStream { + protected final int numBuckets; + protected final Observable bucketedStream; + protected final AtomicReference subscription = new AtomicReference(null); + + private final Func1, Observable> reduceBucketToSummary; + + private final BehaviorSubject counterSubject = BehaviorSubject.create(getEmptyOutputValue()); + + protected BucketedCounterStream(final HystrixEventStream inputEventStream, final int numBuckets, final int bucketSizeInMs, + final Func2 appendRawEventToBucket) { + this.numBuckets = numBuckets; + this.reduceBucketToSummary = new Func1, Observable>() { + @Override + public Observable call(Observable eventBucket) { + return eventBucket.reduce(getEmptyBucketSummary(), appendRawEventToBucket); + } + }; + + final List emptyEventCountsToStart = new ArrayList(); + for (int i = 0; i < numBuckets; i++) { + emptyEventCountsToStart.add(getEmptyBucketSummary()); + } + + this.bucketedStream = Observable.defer(new Func0>() { + @Override + public Observable call() { + return inputEventStream + .observe() + .window(bucketSizeInMs, TimeUnit.MILLISECONDS) //bucket it by the counter window so we can emit to the next operator in time chunks, not on every OnNext + .flatMap(reduceBucketToSummary) //for a given bucket, turn it into a long array containing counts of event types + .startWith(emptyEventCountsToStart); //start it with empty arrays to make consumer logic as generic as possible (windows are always full) + } + }); + } + + abstract Bucket getEmptyBucketSummary(); + + abstract Output getEmptyOutputValue(); + + /** + * Return the stream of buckets + * @return stream of buckets + */ + public abstract Observable observe(); + + public void startCachingStreamValuesIfUnstarted() { + if (subscription.get() == null) { + //the stream is not yet started + Subscription candidateSubscription = observe().subscribe(counterSubject); + if (subscription.compareAndSet(null, candidateSubscription)) { + //won the race to set the subscription + } else { + //lost the race to set the subscription, so we need to cancel this one + candidateSubscription.unsubscribe(); + } + } + } + + /** + * Synchronous call to retrieve the last calculated bucket without waiting for any emissions + * @return last calculated bucket + */ + public Output getLatest() { + startCachingStreamValuesIfUnstarted(); + if (counterSubject.hasValue()) { + return counterSubject.getValue(); + } else { + return getEmptyOutputValue(); + } + } + + public void unsubscribe() { + Subscription s = subscription.get(); + if (s != null) { + s.unsubscribe(); + subscription.compareAndSet(s, null); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedCumulativeCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedCumulativeCounterStream.java new file mode 100644 index 0000000..2c9c8de --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedCumulativeCounterStream.java @@ -0,0 +1,65 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.metric.HystrixEvent; +import com.netflix.hystrix.metric.HystrixEventStream; +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Func2; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Refinement of {@link BucketedCounterStream} which accumulates counters infinitely in the bucket-reduction step + * + * @param type of raw data that needs to get summarized into a bucket + * @param type of data contained in each bucket + * @param type of data emitted to stream subscribers (often is the same as A but does not have to be) + */ +public abstract class BucketedCumulativeCounterStream extends BucketedCounterStream { + private Observable sourceStream; + private final AtomicBoolean isSourceCurrentlySubscribed = new AtomicBoolean(false); + + protected BucketedCumulativeCounterStream(HystrixEventStream stream, int numBuckets, int bucketSizeInMs, + Func2 reduceCommandCompletion, + Func2 reduceBucket) { + super(stream, numBuckets, bucketSizeInMs, reduceCommandCompletion); + + this.sourceStream = bucketedStream + .scan(getEmptyOutputValue(), reduceBucket) + .skip(numBuckets) + .doOnSubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(true); + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(false); + } + }) + .share() //multiple subscribers should get same data + .onBackpressureDrop(); //if there are slow consumers, data should not buffer + } + + @Override + public Observable observe() { + return sourceStream; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedRollingCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedRollingCounterStream.java new file mode 100644 index 0000000..35839bf --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/BucketedRollingCounterStream.java @@ -0,0 +1,75 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.metric.HystrixEvent; +import com.netflix.hystrix.metric.HystrixEventStream; +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.functions.Func2; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Refinement of {@link BucketedCounterStream} which reduces numBuckets at a time. + * + * @param type of raw data that needs to get summarized into a bucket + * @param type of data contained in each bucket + * @param type of data emitted to stream subscribers (often is the same as A but does not have to be) + */ +public abstract class BucketedRollingCounterStream extends BucketedCounterStream { + private Observable sourceStream; + private final AtomicBoolean isSourceCurrentlySubscribed = new AtomicBoolean(false); + + protected BucketedRollingCounterStream(HystrixEventStream stream, final int numBuckets, int bucketSizeInMs, + final Func2 appendRawEventToBucket, + final Func2 reduceBucket) { + super(stream, numBuckets, bucketSizeInMs, appendRawEventToBucket); + Func1, Observable> reduceWindowToSummary = new Func1, Observable>() { + @Override + public Observable call(Observable window) { + return window.scan(getEmptyOutputValue(), reduceBucket).skip(numBuckets); + } + }; + this.sourceStream = bucketedStream //stream broken up into buckets + .window(numBuckets, 1) //emit overlapping windows of buckets + .flatMap(reduceWindowToSummary) //convert a window of bucket-summaries into a single summary + .doOnSubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(true); + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(false); + } + }) + .share() //multiple subscribers should get same data + .onBackpressureDrop(); //if there are slow consumers, data should not buffer + } + + @Override + public Observable observe() { + return sourceStream; + } + + /* package-private */ boolean isSourceCurrentlySubscribed() { + return isSourceCurrentlySubscribed.get(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeCollapserEventCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeCollapserEventCounterStream.java new file mode 100644 index 0000000..92ea206 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeCollapserEventCounterStream.java @@ -0,0 +1,98 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.HystrixCollapserEvent; +import com.netflix.hystrix.metric.HystrixCollapserEventStream; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of event counters for a given Command. + * There is no rolling window abstraction on this stream - every event since the start of the JVM is kept track of. + * The event counters object is calculated on the same schedule as the rolling abstract {@link RollingCommandEventCounterStream}, + * so bucket rolls correspond to new data in this stream, though data never goes out of window in this stream. + * + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link com.netflix.hystrix.HystrixCollapserProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link com.netflix.hystrix.HystrixCollapserProperties#metricsRollingStatisticalWindowBuckets()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class. This value (the latest observed value) may be queried using {@link #getLatest(HystrixEventType.Collapser)}. + */ +public class CumulativeCollapserEventCounterStream extends BucketedCumulativeCounterStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final int NUM_EVENT_TYPES = HystrixEventType.Collapser.values().length; + + public static CumulativeCollapserEventCounterStream getInstance(HystrixCollapserKey collapserKey, HystrixCollapserProperties properties) { + final int counterMetricWindow = properties.metricsRollingStatisticalWindowInMilliseconds().get(); + final int numCounterBuckets = properties.metricsRollingStatisticalWindowBuckets().get(); + final int counterBucketSizeInMs = counterMetricWindow / numCounterBuckets; + + return getInstance(collapserKey, numCounterBuckets, counterBucketSizeInMs); + } + + public static CumulativeCollapserEventCounterStream getInstance(HystrixCollapserKey collapserKey, int numBuckets, int bucketSizeInMs) { + CumulativeCollapserEventCounterStream initialStream = streams.get(collapserKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (CumulativeCollapserEventCounterStream.class) { + CumulativeCollapserEventCounterStream existingStream = streams.get(collapserKey.name()); + if (existingStream == null) { + CumulativeCollapserEventCounterStream newStream = new CumulativeCollapserEventCounterStream(collapserKey, numBuckets, bucketSizeInMs, HystrixCollapserMetrics.appendEventToBucket, HystrixCollapserMetrics.bucketAggregator); + streams.putIfAbsent(collapserKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private CumulativeCollapserEventCounterStream(HystrixCollapserKey collapserKey, int numCounterBuckets, int counterBucketSizeInMs, + Func2 appendEventToBucket, + Func2 reduceBucket) { + super(HystrixCollapserEventStream.getInstance(collapserKey), numCounterBuckets, counterBucketSizeInMs, appendEventToBucket, reduceBucket); + } + + @Override + long[] getEmptyBucketSummary() { + return new long[NUM_EVENT_TYPES]; + } + + @Override + long[] getEmptyOutputValue() { + return new long[NUM_EVENT_TYPES]; + } + + public long getLatest(HystrixEventType.Collapser eventType) { + return getLatest()[eventType.ordinal()]; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeCommandEventCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeCommandEventCounterStream.java new file mode 100644 index 0000000..8ba2f0b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeCommandEventCounterStream.java @@ -0,0 +1,99 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.HystrixCommandCompletionStream; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of event counters for a given Command. + * There is no rolling window abstraction on this stream - every event since the start of the JVM is kept track of. + * The event counters object is calculated on the same schedule as the rolling abstract {@link RollingCommandEventCounterStream}, + * so bucket rolls correspond to new data in this stream, though data never goes out of window in this stream. + * + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixCommandProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link HystrixCommandProperties#metricsRollingStatisticalWindowBuckets()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class. This value (the latest observed value) may be queried using {@link #getLatest(HystrixEventType)}. + */ +public class CumulativeCommandEventCounterStream extends BucketedCumulativeCounterStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final int NUM_EVENT_TYPES = HystrixEventType.values().length; + + public static CumulativeCommandEventCounterStream getInstance(HystrixCommandKey commandKey, HystrixCommandProperties properties) { + final int counterMetricWindow = properties.metricsRollingStatisticalWindowInMilliseconds().get(); + final int numCounterBuckets = properties.metricsRollingStatisticalWindowBuckets().get(); + final int counterBucketSizeInMs = counterMetricWindow / numCounterBuckets; + + return getInstance(commandKey, numCounterBuckets, counterBucketSizeInMs); + } + + public static CumulativeCommandEventCounterStream getInstance(HystrixCommandKey commandKey, int numBuckets, int bucketSizeInMs) { + CumulativeCommandEventCounterStream initialStream = streams.get(commandKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (CumulativeCommandEventCounterStream.class) { + CumulativeCommandEventCounterStream existingStream = streams.get(commandKey.name()); + if (existingStream == null) { + CumulativeCommandEventCounterStream newStream = new CumulativeCommandEventCounterStream(commandKey, numBuckets, bucketSizeInMs, + HystrixCommandMetrics.appendEventToBucket, HystrixCommandMetrics.bucketAggregator); + streams.putIfAbsent(commandKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private CumulativeCommandEventCounterStream(HystrixCommandKey commandKey, int numCounterBuckets, int counterBucketSizeInMs, + Func2 reduceCommandCompletion, + Func2 reduceBucket) { + super(HystrixCommandCompletionStream.getInstance(commandKey), numCounterBuckets, counterBucketSizeInMs, reduceCommandCompletion, reduceBucket); + } + + @Override + long[] getEmptyBucketSummary() { + return new long[NUM_EVENT_TYPES]; + } + + @Override + long[] getEmptyOutputValue() { + return new long[NUM_EVENT_TYPES]; + } + + public long getLatest(HystrixEventType eventType) { + return getLatest()[eventType.ordinal()]; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeThreadPoolEventCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeThreadPoolEventCounterStream.java new file mode 100644 index 0000000..5852a59 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/CumulativeThreadPoolEventCounterStream.java @@ -0,0 +1,100 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.HystrixThreadPoolCompletionStream; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of event counters for a given ThreadPool. + * There is a rolling window abstraction on this stream. + * The event counters object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowBuckets()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class. + * You may query to find the latest rolling count of 2 events (executed/rejected) via {@link #getLatestCount(com.netflix.hystrix.HystrixEventType.ThreadPool)}. + */ +public class CumulativeThreadPoolEventCounterStream extends BucketedCumulativeCounterStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final int ALL_EVENT_TYPES_SIZE = HystrixEventType.ThreadPool.values().length; + + public static CumulativeThreadPoolEventCounterStream getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties properties) { + final int counterMetricWindow = properties.metricsRollingStatisticalWindowInMilliseconds().get(); + final int numCounterBuckets = properties.metricsRollingStatisticalWindowBuckets().get(); + final int counterBucketSizeInMs = counterMetricWindow / numCounterBuckets; + + return getInstance(threadPoolKey, numCounterBuckets, counterBucketSizeInMs); + } + + public static CumulativeThreadPoolEventCounterStream getInstance(HystrixThreadPoolKey threadPoolKey, int numBuckets, int bucketSizeInMs) { + CumulativeThreadPoolEventCounterStream initialStream = streams.get(threadPoolKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (CumulativeThreadPoolEventCounterStream.class) { + CumulativeThreadPoolEventCounterStream existingStream = streams.get(threadPoolKey.name()); + if (existingStream == null) { + CumulativeThreadPoolEventCounterStream newStream = + new CumulativeThreadPoolEventCounterStream(threadPoolKey, numBuckets, bucketSizeInMs, + HystrixThreadPoolMetrics.appendEventToBucket, HystrixThreadPoolMetrics.counterAggregator); + streams.putIfAbsent(threadPoolKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + + private CumulativeThreadPoolEventCounterStream(HystrixThreadPoolKey threadPoolKey, int numCounterBuckets, int counterBucketSizeInMs, + Func2 reduceCommandCompletion, + Func2 reduceBucket) { + super(HystrixThreadPoolCompletionStream.getInstance(threadPoolKey), numCounterBuckets, counterBucketSizeInMs, reduceCommandCompletion, reduceBucket); + } + + @Override + public long[] getEmptyBucketSummary() { + return new long[ALL_EVENT_TYPES_SIZE]; + } + + @Override + public long[] getEmptyOutputValue() { + return new long[ALL_EVENT_TYPES_SIZE]; + } + + public long getLatestCount(HystrixEventType.ThreadPool eventType) { + return getLatest()[eventType.ordinal()]; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/HealthCountsStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/HealthCountsStream.java new file mode 100644 index 0000000..82b95b8 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/HealthCountsStream.java @@ -0,0 +1,110 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.HystrixCommandCompletionStream; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of rolling health counts for a given Command. + * There is a rolling window abstraction on this stream. + * The HealthCounts object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new HealthCounts object is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixCommandProperties#metricsHealthSnapshotIntervalInMilliseconds()} + * b = {@link HystrixCommandProperties#metricsRollingStatisticalWindowBuckets()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class. This value (the latest observed value) may be queried using {@link #getLatest()}. + */ +public class HealthCountsStream extends BucketedRollingCounterStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final int NUM_EVENT_TYPES = HystrixEventType.values().length; + + private static final Func2 healthCheckAccumulator = new Func2() { + @Override + public HystrixCommandMetrics.HealthCounts call(HystrixCommandMetrics.HealthCounts healthCounts, long[] bucketEventCounts) { + return healthCounts.plus(bucketEventCounts); + } + }; + + + public static HealthCountsStream getInstance(HystrixCommandKey commandKey, HystrixCommandProperties properties) { + final int healthCountBucketSizeInMs = properties.metricsHealthSnapshotIntervalInMilliseconds().get(); + if (healthCountBucketSizeInMs == 0) { + throw new RuntimeException("You have set the bucket size to 0ms. Please set a positive number, so that the metric stream can be properly consumed"); + } + final int numHealthCountBuckets = properties.metricsRollingStatisticalWindowInMilliseconds().get() / healthCountBucketSizeInMs; + + return getInstance(commandKey, numHealthCountBuckets, healthCountBucketSizeInMs); + } + + public static HealthCountsStream getInstance(HystrixCommandKey commandKey, int numBuckets, int bucketSizeInMs) { + HealthCountsStream initialStream = streams.get(commandKey.name()); + if (initialStream != null) { + return initialStream; + } else { + final HealthCountsStream healthStream; + synchronized (HealthCountsStream.class) { + HealthCountsStream existingStream = streams.get(commandKey.name()); + if (existingStream == null) { + HealthCountsStream newStream = new HealthCountsStream(commandKey, numBuckets, bucketSizeInMs, + HystrixCommandMetrics.appendEventToBucket); + + streams.putIfAbsent(commandKey.name(), newStream); + healthStream = newStream; + } else { + healthStream = existingStream; + } + } + healthStream.startCachingStreamValuesIfUnstarted(); + return healthStream; + } + } + + public static void reset() { + streams.clear(); + } + + public static void removeByKey(HystrixCommandKey key) { + streams.remove(key.name()); + } + + private HealthCountsStream(final HystrixCommandKey commandKey, final int numBuckets, final int bucketSizeInMs, + Func2 reduceCommandCompletion) { + super(HystrixCommandCompletionStream.getInstance(commandKey), numBuckets, bucketSizeInMs, reduceCommandCompletion, healthCheckAccumulator); + } + + @Override + long[] getEmptyBucketSummary() { + return new long[NUM_EVENT_TYPES]; + } + + @Override + HystrixCommandMetrics.HealthCounts getEmptyOutputValue() { + return HystrixCommandMetrics.HealthCounts.empty(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/HystrixDashboardStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/HystrixDashboardStream.java new file mode 100644 index 0000000..7913424 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/HystrixDashboardStream.java @@ -0,0 +1,116 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Func1; + +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class HystrixDashboardStream { + final int delayInMs; + final Observable singleSource; + final AtomicBoolean isSourceCurrentlySubscribed = new AtomicBoolean(false); + + private static final DynamicIntProperty dataEmissionIntervalInMs = + DynamicPropertyFactory.getInstance().getIntProperty("hystrix.stream.dashboard.intervalInMilliseconds", 500); + + private HystrixDashboardStream(int delayInMs) { + this.delayInMs = delayInMs; + this.singleSource = Observable.interval(delayInMs, TimeUnit.MILLISECONDS) + .map(new Func1() { + @Override + public DashboardData call(Long timestamp) { + return new DashboardData( + HystrixCommandMetrics.getInstances(), + HystrixThreadPoolMetrics.getInstances(), + HystrixCollapserMetrics.getInstances() + ); + } + }) + .doOnSubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(true); + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(false); + } + }) + .share() + .onBackpressureDrop(); + } + + //The data emission interval is looked up on startup only + private static final HystrixDashboardStream INSTANCE = + new HystrixDashboardStream(dataEmissionIntervalInMs.get()); + + public static HystrixDashboardStream getInstance() { + return INSTANCE; + } + + static HystrixDashboardStream getNonSingletonInstanceOnlyUsedInUnitTests(int delayInMs) { + return new HystrixDashboardStream(delayInMs); + } + + /** + * Return a ref-counted stream that will only do work when at least one subscriber is present + */ + public Observable observe() { + return singleSource; + } + + public boolean isSourceCurrentlySubscribed() { + return isSourceCurrentlySubscribed.get(); + } + + public static class DashboardData { + final Collection commandMetrics; + final Collection threadPoolMetrics; + final Collection collapserMetrics; + + public DashboardData(Collection commandMetrics, Collection threadPoolMetrics, Collection collapserMetrics) { + this.commandMetrics = commandMetrics; + this.threadPoolMetrics = threadPoolMetrics; + this.collapserMetrics = collapserMetrics; + } + + public Collection getCommandMetrics() { + return commandMetrics; + } + + public Collection getThreadPoolMetrics() { + return threadPoolMetrics; + } + + public Collection getCollapserMetrics() { + return collapserMetrics; + } + } +} + + diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCollapserBatchSizeDistributionStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCollapserBatchSizeDistributionStream.java new file mode 100644 index 0000000..0b4a62b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCollapserBatchSizeDistributionStream.java @@ -0,0 +1,94 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.metric.HystrixCollapserEvent; +import com.netflix.hystrix.metric.HystrixCollapserEventStream; +import org.HdrHistogram.Histogram; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of batch size distributions for a given Command. + * There is a rolling window abstraction on this stream. + * The latency distribution object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixCollapserProperties#metricsRollingPercentileWindowInMilliseconds()} + * b = {@link HystrixCollapserProperties#metricsRollingPercentileBucketSize()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class, as soon as this stream is queried for the first time. + */ +public class RollingCollapserBatchSizeDistributionStream extends RollingDistributionStream { + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final Func2 addValuesToBucket = new Func2() { + @Override + public Histogram call(Histogram initialDistribution, HystrixCollapserEvent event) { + switch (event.getEventType()) { + case ADDED_TO_BATCH: + if (event.getCount() > -1) { + initialDistribution.recordValue(event.getCount()); + } + break; + default: + //do nothing + break; + } + return initialDistribution; + } + }; + + public static RollingCollapserBatchSizeDistributionStream getInstance(HystrixCollapserKey collapserKey, HystrixCollapserProperties properties) { + final int percentileMetricWindow = properties.metricsRollingPercentileWindowInMilliseconds().get(); + final int numPercentileBuckets = properties.metricsRollingPercentileWindowBuckets().get(); + final int percentileBucketSizeInMs = percentileMetricWindow / numPercentileBuckets; + + return getInstance(collapserKey, numPercentileBuckets, percentileBucketSizeInMs); + } + + public static RollingCollapserBatchSizeDistributionStream getInstance(HystrixCollapserKey collapserKey, int numBuckets, int bucketSizeInMs) { + RollingCollapserBatchSizeDistributionStream initialStream = streams.get(collapserKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (RollingCollapserBatchSizeDistributionStream.class) { + RollingCollapserBatchSizeDistributionStream existingStream = streams.get(collapserKey.name()); + if (existingStream == null) { + RollingCollapserBatchSizeDistributionStream newStream = new RollingCollapserBatchSizeDistributionStream(collapserKey, numBuckets, bucketSizeInMs); + streams.putIfAbsent(collapserKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private RollingCollapserBatchSizeDistributionStream(HystrixCollapserKey collapserKey, int numPercentileBuckets, int percentileBucketSizeInMs) { + super(HystrixCollapserEventStream.getInstance(collapserKey), numPercentileBuckets, percentileBucketSizeInMs, addValuesToBucket); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCollapserEventCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCollapserEventCounterStream.java new file mode 100644 index 0000000..71f8c52 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCollapserEventCounterStream.java @@ -0,0 +1,96 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.HystrixCollapserEvent; +import com.netflix.hystrix.metric.HystrixCollapserEventStream; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of event counters for a given Command. + * There is a rolling window abstraction on this stream. + * The event counters object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link com.netflix.hystrix.HystrixCollapserProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link com.netflix.hystrix.HystrixCollapserProperties#metricsRollingStatisticalWindowBuckets()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class. This value (the latest observed value) may be queried using {@link #getLatest(HystrixEventType.Collapser)}. + */ +public class RollingCollapserEventCounterStream extends BucketedRollingCounterStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final int NUM_EVENT_TYPES = HystrixEventType.Collapser.values().length; + + public static RollingCollapserEventCounterStream getInstance(HystrixCollapserKey collapserKey, HystrixCollapserProperties properties) { + final int counterMetricWindow = properties.metricsRollingStatisticalWindowInMilliseconds().get(); + final int numCounterBuckets = properties.metricsRollingStatisticalWindowBuckets().get(); + final int counterBucketSizeInMs = counterMetricWindow / numCounterBuckets; + + return getInstance(collapserKey, numCounterBuckets, counterBucketSizeInMs); + } + + public static RollingCollapserEventCounterStream getInstance(HystrixCollapserKey collapserKey, int numBuckets, int bucketSizeInMs) { + RollingCollapserEventCounterStream initialStream = streams.get(collapserKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (RollingCollapserEventCounterStream.class) { + RollingCollapserEventCounterStream existingStream = streams.get(collapserKey.name()); + if (existingStream == null) { + RollingCollapserEventCounterStream newStream = new RollingCollapserEventCounterStream(collapserKey, numBuckets, bucketSizeInMs, HystrixCollapserMetrics.appendEventToBucket, HystrixCollapserMetrics.bucketAggregator); + streams.putIfAbsent(collapserKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private RollingCollapserEventCounterStream(HystrixCollapserKey collapserKey, int numCounterBuckets, int counterBucketSizeInMs, + Func2 appendEventToBucket, + Func2 reduceBucket) { + super(HystrixCollapserEventStream.getInstance(collapserKey), numCounterBuckets, counterBucketSizeInMs, appendEventToBucket, reduceBucket); + } + + @Override + long[] getEmptyBucketSummary() { + return new long[NUM_EVENT_TYPES]; + } + + @Override + long[] getEmptyOutputValue() { + return new long[NUM_EVENT_TYPES]; + } + + public long getLatest(HystrixEventType.Collapser eventType) { + return getLatest()[eventType.ordinal()]; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandEventCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandEventCounterStream.java new file mode 100644 index 0000000..bac3634 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandEventCounterStream.java @@ -0,0 +1,97 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.HystrixCommandCompletionStream; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of event counters for a given Command. + * There is a rolling window abstraction on this stream. + * The event counters object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixCommandProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link HystrixCommandProperties#metricsRollingStatisticalWindowBuckets()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class. This value (the latest observed value) may be queried using {@link #getLatest(HystrixEventType)}. + */ +public class RollingCommandEventCounterStream extends BucketedRollingCounterStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final int NUM_EVENT_TYPES = HystrixEventType.values().length; + + public static RollingCommandEventCounterStream getInstance(HystrixCommandKey commandKey, HystrixCommandProperties properties) { + final int counterMetricWindow = properties.metricsRollingStatisticalWindowInMilliseconds().get(); + final int numCounterBuckets = properties.metricsRollingStatisticalWindowBuckets().get(); + final int counterBucketSizeInMs = counterMetricWindow / numCounterBuckets; + + return getInstance(commandKey, numCounterBuckets, counterBucketSizeInMs); + } + + public static RollingCommandEventCounterStream getInstance(HystrixCommandKey commandKey, int numBuckets, int bucketSizeInMs) { + RollingCommandEventCounterStream initialStream = streams.get(commandKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (RollingCommandEventCounterStream.class) { + RollingCommandEventCounterStream existingStream = streams.get(commandKey.name()); + if (existingStream == null) { + RollingCommandEventCounterStream newStream = new RollingCommandEventCounterStream(commandKey, numBuckets, bucketSizeInMs, + HystrixCommandMetrics.appendEventToBucket, HystrixCommandMetrics.bucketAggregator); + streams.putIfAbsent(commandKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private RollingCommandEventCounterStream(HystrixCommandKey commandKey, int numCounterBuckets, int counterBucketSizeInMs, + Func2 reduceCommandCompletion, + Func2 reduceBucket) { + super(HystrixCommandCompletionStream.getInstance(commandKey), numCounterBuckets, counterBucketSizeInMs, reduceCommandCompletion, reduceBucket); + } + + @Override + long[] getEmptyBucketSummary() { + return new long[NUM_EVENT_TYPES]; + } + + @Override + long[] getEmptyOutputValue() { + return new long[NUM_EVENT_TYPES]; + } + + public long getLatest(HystrixEventType eventType) { + return getLatest()[eventType.ordinal()]; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandLatencyDistributionStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandLatencyDistributionStream.java new file mode 100644 index 0000000..4e4308e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandLatencyDistributionStream.java @@ -0,0 +1,95 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.HystrixCommandCompletionStream; +import com.netflix.hystrix.metric.HystrixCommandEvent; +import org.HdrHistogram.Histogram; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of latency distributions for a given Command. + * There is a rolling window abstraction on this stream. + * The latency distribution object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixCommandProperties#metricsRollingPercentileWindowInMilliseconds()} + * b = {@link HystrixCommandProperties#metricsRollingPercentileBucketSize()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * The only latencies which get included in the distribution are for those commands which started execution. + * This relies on {@link HystrixCommandEvent#didCommandExecute()} + * + * These values get produced and cached in this class. + * The distributions can be queried on 2 dimensions: + * * Execution time or total time + * ** Execution time is the time spent executing the user-provided execution method. + * ** Total time is the time spent from the perspecitve of the consumer, and includes all Hystrix bookkeeping. + */ +public class RollingCommandLatencyDistributionStream extends RollingDistributionStream { + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final Func2 addValuesToBucket = new Func2() { + @Override + public Histogram call(Histogram initialDistribution, HystrixCommandCompletion event) { + if (event.didCommandExecute() && event.getExecutionLatency() > -1) { + initialDistribution.recordValue(event.getExecutionLatency()); + } + return initialDistribution; + } + }; + + public static RollingCommandLatencyDistributionStream getInstance(HystrixCommandKey commandKey, HystrixCommandProperties properties) { + final int percentileMetricWindow = properties.metricsRollingPercentileWindowInMilliseconds().get(); + final int numPercentileBuckets = properties.metricsRollingPercentileWindowBuckets().get(); + final int percentileBucketSizeInMs = percentileMetricWindow / numPercentileBuckets; + + return getInstance(commandKey, numPercentileBuckets, percentileBucketSizeInMs); + } + + public static RollingCommandLatencyDistributionStream getInstance(HystrixCommandKey commandKey, int numBuckets, int bucketSizeInMs) { + RollingCommandLatencyDistributionStream initialStream = streams.get(commandKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (RollingCommandLatencyDistributionStream.class) { + RollingCommandLatencyDistributionStream existingStream = streams.get(commandKey.name()); + if (existingStream == null) { + RollingCommandLatencyDistributionStream newStream = new RollingCommandLatencyDistributionStream(commandKey, numBuckets, bucketSizeInMs); + streams.putIfAbsent(commandKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private RollingCommandLatencyDistributionStream(HystrixCommandKey commandKey, int numPercentileBuckets, int percentileBucketSizeInMs) { + super(HystrixCommandCompletionStream.getInstance(commandKey), numPercentileBuckets, percentileBucketSizeInMs, addValuesToBucket); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandMaxConcurrencyStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandMaxConcurrencyStream.java new file mode 100644 index 0000000..16ea3c4 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandMaxConcurrencyStream.java @@ -0,0 +1,74 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.metric.HystrixCommandStartStream; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of the maximum concurrency seen by this command. + * + * This gets calculated using a rolling window of t1 milliseconds. This window has b buckets. + * Therefore, a new rolling-max is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixCommandProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link HystrixCommandProperties#metricsRollingStatisticalWindowBuckets()} + * + * This value gets cached in this class. It may be queried using {@link #getLatestRollingMax()} + * This value is stable - there's no peeking into a bucket until it is emitted + * + */ +public class RollingCommandMaxConcurrencyStream extends RollingConcurrencyStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + public static RollingCommandMaxConcurrencyStream getInstance(HystrixCommandKey commandKey, HystrixCommandProperties properties) { + final int counterMetricWindow = properties.metricsRollingStatisticalWindowInMilliseconds().get(); + final int numCounterBuckets = properties.metricsRollingStatisticalWindowBuckets().get(); + final int counterBucketSizeInMs = counterMetricWindow / numCounterBuckets; + + return getInstance(commandKey, numCounterBuckets, counterBucketSizeInMs); + } + + public static RollingCommandMaxConcurrencyStream getInstance(HystrixCommandKey commandKey, int numBuckets, int bucketSizeInMs) { + RollingCommandMaxConcurrencyStream initialStream = streams.get(commandKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (RollingCommandMaxConcurrencyStream.class) { + RollingCommandMaxConcurrencyStream existingStream = streams.get(commandKey.name()); + if (existingStream == null) { + RollingCommandMaxConcurrencyStream newStream = new RollingCommandMaxConcurrencyStream(commandKey, numBuckets, bucketSizeInMs); + streams.putIfAbsent(commandKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private RollingCommandMaxConcurrencyStream(final HystrixCommandKey commandKey, final int numBuckets, final int bucketSizeInMs) { + super(HystrixCommandStartStream.getInstance(commandKey), numBuckets, bucketSizeInMs); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandUserLatencyDistributionStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandUserLatencyDistributionStream.java new file mode 100644 index 0000000..003d815 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingCommandUserLatencyDistributionStream.java @@ -0,0 +1,97 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixCollapserProperties; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.HystrixCommandCompletionStream; +import com.netflix.hystrix.metric.HystrixCommandEvent; +import org.HdrHistogram.Histogram; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of latency distributions for a given Command. + * There is a rolling window abstraction on this stream. + * The latency distribution object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixCollapserProperties#metricsRollingPercentileWindowInMilliseconds()} + * b = {@link HystrixCollapserProperties#metricsRollingPercentileBucketSize()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * The only latencies which get included in the distribution are for those commands which started execution. + * This relies on {@link HystrixCommandEvent#didCommandExecute()} + * + * These values get produced and cached in this class. + * The distributions can be queried on 2 dimensions: + * * Execution time or total time + * ** Execution time is the time spent executing the user-provided execution method. + * ** Total time is the time spent from the perspecitve of the consumer, and includes all Hystrix bookkeeping. + */ +public class RollingCommandUserLatencyDistributionStream extends RollingDistributionStream { + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final Func2 addValuesToBucket = new Func2() { + @Override + public Histogram call(Histogram initialDistribution, HystrixCommandCompletion event) { + if (event.didCommandExecute() && event.getTotalLatency() > -1) { + initialDistribution.recordValue(event.getTotalLatency()); + } + return initialDistribution; + } + }; + + public static RollingCommandUserLatencyDistributionStream getInstance(HystrixCommandKey commandKey, HystrixCommandProperties properties) { + final int percentileMetricWindow = properties.metricsRollingPercentileWindowInMilliseconds().get(); + final int numPercentileBuckets = properties.metricsRollingPercentileWindowBuckets().get(); + final int percentileBucketSizeInMs = percentileMetricWindow / numPercentileBuckets; + + return getInstance(commandKey, numPercentileBuckets, percentileBucketSizeInMs); + } + + public static RollingCommandUserLatencyDistributionStream getInstance(HystrixCommandKey commandKey, int numBuckets, int bucketSizeInMs) { + RollingCommandUserLatencyDistributionStream initialStream = streams.get(commandKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (RollingCommandUserLatencyDistributionStream.class) { + RollingCommandUserLatencyDistributionStream existingStream = streams.get(commandKey.name()); + if (existingStream == null) { + RollingCommandUserLatencyDistributionStream newStream = new RollingCommandUserLatencyDistributionStream(commandKey, numBuckets, bucketSizeInMs); + streams.putIfAbsent(commandKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private RollingCommandUserLatencyDistributionStream(HystrixCommandKey commandKey, int numPercentileBuckets, int percentileBucketSizeInMs) { + super(HystrixCommandCompletionStream.getInstance(commandKey), numPercentileBuckets, percentileBucketSizeInMs, addValuesToBucket); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingConcurrencyStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingConcurrencyStream.java new file mode 100644 index 0000000..9fd09aa --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingConcurrencyStream.java @@ -0,0 +1,122 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.metric.HystrixCommandExecutionStarted; +import com.netflix.hystrix.metric.HystrixEventStream; +import rx.Observable; +import rx.Subscription; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.subjects.BehaviorSubject; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Maintains a stream of max-concurrency + * + * This gets calculated using a rolling window of t1 milliseconds. This window has b buckets. + * Therefore, a new rolling-max is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixCommandProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link HystrixCommandProperties#metricsRollingStatisticalWindowBuckets()} + * + * This value gets cached in this class. It may be queried using {@link #getLatestRollingMax()} + * + * This is a stable value - there's no peeking into a bucket until it is emitted + */ +public abstract class RollingConcurrencyStream { + private AtomicReference rollingMaxSubscription = new AtomicReference(null); + private final BehaviorSubject rollingMax = BehaviorSubject.create(0); + private final Observable rollingMaxStream; + + private static final Func2 reduceToMax = new Func2() { + @Override + public Integer call(Integer a, Integer b) { + return Math.max(a, b); + } + }; + + private static final Func1, Observable> reduceStreamToMax = new Func1, Observable>() { + @Override + public Observable call(Observable observedConcurrency) { + return observedConcurrency.reduce(0, reduceToMax); + } + }; + + private static final Func1 getConcurrencyCountFromEvent = new Func1() { + @Override + public Integer call(HystrixCommandExecutionStarted event) { + return event.getCurrentConcurrency(); + } + }; + + protected RollingConcurrencyStream(final HystrixEventStream inputEventStream, final int numBuckets, final int bucketSizeInMs) { + final List emptyRollingMaxBuckets = new ArrayList(); + for (int i = 0; i < numBuckets; i++) { + emptyRollingMaxBuckets.add(0); + } + + rollingMaxStream = inputEventStream + .observe() + .map(getConcurrencyCountFromEvent) + .window(bucketSizeInMs, TimeUnit.MILLISECONDS) + .flatMap(reduceStreamToMax) + .startWith(emptyRollingMaxBuckets) + .window(numBuckets, 1) + .flatMap(reduceStreamToMax) + .share() + .onBackpressureDrop(); + } + + public void startCachingStreamValuesIfUnstarted() { + if (rollingMaxSubscription.get() == null) { + //the stream is not yet started + Subscription candidateSubscription = observe().subscribe(rollingMax); + if (rollingMaxSubscription.compareAndSet(null, candidateSubscription)) { + //won the race to set the subscription + } else { + //lost the race to set the subscription, so we need to cancel this one + candidateSubscription.unsubscribe(); + } + } + } + + public long getLatestRollingMax() { + startCachingStreamValuesIfUnstarted(); + if (rollingMax.hasValue()) { + return rollingMax.getValue(); + } else { + return 0L; + } + } + + public Observable observe() { + return rollingMaxStream; + } + + public void unsubscribe() { + Subscription s = rollingMaxSubscription.get(); + if (s != null) { + s.unsubscribe(); + rollingMaxSubscription.compareAndSet(s, null); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingDistributionStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingDistributionStream.java new file mode 100644 index 0000000..95eb514 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingDistributionStream.java @@ -0,0 +1,159 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.metric.CachedValuesHistogram; +import com.netflix.hystrix.metric.HystrixEvent; +import com.netflix.hystrix.metric.HystrixEventStream; +import org.HdrHistogram.Histogram; +import rx.Observable; +import rx.Subscription; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.subjects.BehaviorSubject; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Maintains a stream of distributions for a given Command. + * There is a rolling window abstraction on this stream. + * The latency distribution object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = metricsRollingPercentileWindowInMilliseconds() + * b = metricsRollingPercentileBucketSize() + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class. + */ +public class RollingDistributionStream { + private AtomicReference rollingDistributionSubscription = new AtomicReference(null); + private final BehaviorSubject rollingDistribution = BehaviorSubject.create(CachedValuesHistogram.backedBy(CachedValuesHistogram.getNewHistogram())); + private final Observable rollingDistributionStream; + + private static final Func2 distributionAggregator = new Func2() { + @Override + public Histogram call(Histogram initialDistribution, Histogram distributionToAdd) { + initialDistribution.add(distributionToAdd); + return initialDistribution; + } + }; + + private static final Func1, Observable> reduceWindowToSingleDistribution = new Func1, Observable>() { + @Override + public Observable call(Observable window) { + return window.reduce(distributionAggregator); + } + }; + + private static final Func1 cacheHistogramValues = new Func1() { + @Override + public CachedValuesHistogram call(Histogram histogram) { + return CachedValuesHistogram.backedBy(histogram); + } + }; + + private static final Func1, Observable>> convertToList = + new Func1, Observable>>() { + @Override + public Observable> call(Observable windowOf2) { + return windowOf2.toList(); + } + }; + + protected RollingDistributionStream(final HystrixEventStream stream, final int numBuckets, final int bucketSizeInMs, + final Func2 addValuesToBucket) { + final List emptyDistributionsToStart = new ArrayList(); + for (int i = 0; i < numBuckets; i++) { + emptyDistributionsToStart.add(CachedValuesHistogram.getNewHistogram()); + } + + final Func1, Observable> reduceBucketToSingleDistribution = new Func1, Observable>() { + @Override + public Observable call(Observable bucket) { + return bucket.reduce(CachedValuesHistogram.getNewHistogram(), addValuesToBucket); + } + }; + + rollingDistributionStream = stream + .observe() + .window(bucketSizeInMs, TimeUnit.MILLISECONDS) //stream of unaggregated buckets + .flatMap(reduceBucketToSingleDistribution) //stream of aggregated Histograms + .startWith(emptyDistributionsToStart) //stream of aggregated Histograms that starts with n empty + .window(numBuckets, 1) //windowed stream: each OnNext is a stream of n Histograms + .flatMap(reduceWindowToSingleDistribution) //reduced stream: each OnNext is a single Histogram + .map(cacheHistogramValues) //convert to CachedValueHistogram (commonly-accessed values are cached) + .share() + .onBackpressureDrop(); + } + + public Observable observe() { + return rollingDistributionStream; + } + + public int getLatestMean() { + CachedValuesHistogram latest = getLatest(); + if (latest != null) { + return latest.getMean(); + } else { + return 0; + } + } + + public int getLatestPercentile(double percentile) { + CachedValuesHistogram latest = getLatest(); + if (latest != null) { + return latest.getValueAtPercentile(percentile); + } else { + return 0; + } + } + + public void startCachingStreamValuesIfUnstarted() { + if (rollingDistributionSubscription.get() == null) { + //the stream is not yet started + Subscription candidateSubscription = observe().subscribe(rollingDistribution); + if (rollingDistributionSubscription.compareAndSet(null, candidateSubscription)) { + //won the race to set the subscription + } else { + //lost the race to set the subscription, so we need to cancel this one + candidateSubscription.unsubscribe(); + } + } + } + + CachedValuesHistogram getLatest() { + startCachingStreamValuesIfUnstarted(); + if (rollingDistribution.hasValue()) { + return rollingDistribution.getValue(); + } else { + return null; + } + } + + public void unsubscribe() { + Subscription s = rollingDistributionSubscription.get(); + if (s != null) { + s.unsubscribe(); + rollingDistributionSubscription.compareAndSet(s, null); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolEventCounterStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolEventCounterStream.java new file mode 100644 index 0000000..86cdd3a --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolEventCounterStream.java @@ -0,0 +1,99 @@ +/** + * 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.metric.consumer; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.metric.HystrixCommandCompletion; +import com.netflix.hystrix.metric.HystrixThreadPoolCompletionStream; +import rx.functions.Func2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of event counters for a given ThreadPool. + * There is a rolling window abstraction on this stream. + * The event counters object is calculated over a window of t1 milliseconds. This window has b buckets. + * Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowBuckets()} + * + * These values are stable - there's no peeking into a bucket until it is emitted + * + * These values get produced and cached in this class. + * You may query to find the latest rolling count of 2 events (executed/rejected) via {@link #getLatestCount(com.netflix.hystrix.HystrixEventType.ThreadPool)}. + */ +public class RollingThreadPoolEventCounterStream extends BucketedRollingCounterStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + private static final int ALL_EVENT_TYPES_SIZE = HystrixEventType.ThreadPool.values().length; + + public static RollingThreadPoolEventCounterStream getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties properties) { + final int counterMetricWindow = properties.metricsRollingStatisticalWindowInMilliseconds().get(); + final int numCounterBuckets = properties.metricsRollingStatisticalWindowBuckets().get(); + final int counterBucketSizeInMs = counterMetricWindow / numCounterBuckets; + + return getInstance(threadPoolKey, numCounterBuckets, counterBucketSizeInMs); + } + + public static RollingThreadPoolEventCounterStream getInstance(HystrixThreadPoolKey threadPoolKey, int numBuckets, int bucketSizeInMs) { + RollingThreadPoolEventCounterStream initialStream = streams.get(threadPoolKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (RollingThreadPoolEventCounterStream.class) { + RollingThreadPoolEventCounterStream existingStream = streams.get(threadPoolKey.name()); + if (existingStream == null) { + RollingThreadPoolEventCounterStream newStream = + new RollingThreadPoolEventCounterStream(threadPoolKey, numBuckets, bucketSizeInMs, + HystrixThreadPoolMetrics.appendEventToBucket, HystrixThreadPoolMetrics.counterAggregator); + streams.putIfAbsent(threadPoolKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + private RollingThreadPoolEventCounterStream(HystrixThreadPoolKey threadPoolKey, int numCounterBuckets, int counterBucketSizeInMs, + Func2 reduceCommandCompletion, + Func2 reduceBucket) { + super(HystrixThreadPoolCompletionStream.getInstance(threadPoolKey), numCounterBuckets, counterBucketSizeInMs, reduceCommandCompletion, reduceBucket); + } + + @Override + public long[] getEmptyBucketSummary() { + return new long[ALL_EVENT_TYPES_SIZE]; + } + + @Override + public long[] getEmptyOutputValue() { + return new long[ALL_EVENT_TYPES_SIZE]; + } + + public long getLatestCount(HystrixEventType.ThreadPool eventType) { + return getLatest()[eventType.ordinal()]; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolMaxConcurrencyStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolMaxConcurrencyStream.java new file mode 100644 index 0000000..a4e94fe --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolMaxConcurrencyStream.java @@ -0,0 +1,75 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.metric.HystrixThreadPoolStartStream; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maintains a stream of max-concurrency + * + * This gets calculated using a rolling window of t1 milliseconds. This window has b buckets. + * Therefore, a new rolling-max is produced every t2 (=t1/b) milliseconds + * t1 = {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowInMilliseconds()} + * b = {@link HystrixThreadPoolProperties#metricsRollingStatisticalWindowBuckets()} + * + * This value gets cached in this class. It may be queried using {@link #getLatestRollingMax()} + * + * This is a stable value - there's no peeking into a bucket until it is emitted + */ +public class RollingThreadPoolMaxConcurrencyStream extends RollingConcurrencyStream { + + private static final ConcurrentMap streams = new ConcurrentHashMap(); + + public static RollingThreadPoolMaxConcurrencyStream getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties properties) { + final int counterMetricWindow = properties.metricsRollingStatisticalWindowInMilliseconds().get(); + final int numCounterBuckets = properties.metricsRollingStatisticalWindowBuckets().get(); + final int counterBucketSizeInMs = counterMetricWindow / numCounterBuckets; + + return getInstance(threadPoolKey, numCounterBuckets, counterBucketSizeInMs); + } + + public static RollingThreadPoolMaxConcurrencyStream getInstance(HystrixThreadPoolKey threadPoolKey, int numBuckets, int bucketSizeInMs) { + RollingThreadPoolMaxConcurrencyStream initialStream = streams.get(threadPoolKey.name()); + if (initialStream != null) { + return initialStream; + } else { + synchronized (RollingThreadPoolMaxConcurrencyStream.class) { + RollingThreadPoolMaxConcurrencyStream existingStream = streams.get(threadPoolKey.name()); + if (existingStream == null) { + RollingThreadPoolMaxConcurrencyStream newStream = + new RollingThreadPoolMaxConcurrencyStream(threadPoolKey, numBuckets, bucketSizeInMs); + streams.putIfAbsent(threadPoolKey.name(), newStream); + return newStream; + } else { + return existingStream; + } + } + } + } + + public static void reset() { + streams.clear(); + } + + public RollingThreadPoolMaxConcurrencyStream(final HystrixThreadPoolKey threadPoolKey, final int numBuckets, final int bucketSizeInMs) { + super(HystrixThreadPoolStartStream.getInstance(threadPoolKey), numBuckets, bucketSizeInMs); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixCommandUtilization.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixCommandUtilization.java new file mode 100644 index 0000000..1a35125 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixCommandUtilization.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 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.metric.sample; + +import com.netflix.hystrix.HystrixCommandMetrics; + +public class HystrixCommandUtilization { + private final int concurrentCommandCount; + + public HystrixCommandUtilization(int concurrentCommandCount) { + this.concurrentCommandCount = concurrentCommandCount; + } + + public static HystrixCommandUtilization sample(HystrixCommandMetrics commandMetrics) { + return new HystrixCommandUtilization(commandMetrics.getCurrentConcurrentExecutionCount()); + } + + public int getConcurrentCommandCount() { + return concurrentCommandCount; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixThreadPoolUtilization.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixThreadPoolUtilization.java new file mode 100644 index 0000000..ceba4f5 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixThreadPoolUtilization.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016 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.metric.sample; + +import com.netflix.hystrix.HystrixThreadPoolMetrics; + +public class HystrixThreadPoolUtilization { + private final int currentActiveCount; + private final int currentCorePoolSize; + private final int currentPoolSize; + private final int currentQueueSize; + + public HystrixThreadPoolUtilization(int currentActiveCount, int currentCorePoolSize, int currentPoolSize, int currentQueueSize) { + this.currentActiveCount = currentActiveCount; + this.currentCorePoolSize = currentCorePoolSize; + this.currentPoolSize = currentPoolSize; + this.currentQueueSize = currentQueueSize; + } + + public static HystrixThreadPoolUtilization sample(HystrixThreadPoolMetrics threadPoolMetrics) { + return new HystrixThreadPoolUtilization( + threadPoolMetrics.getCurrentActiveCount().intValue(), + threadPoolMetrics.getCurrentCorePoolSize().intValue(), + threadPoolMetrics.getCurrentPoolSize().intValue(), + threadPoolMetrics.getCurrentQueueSize().intValue() + ); + } + + public int getCurrentActiveCount() { + return currentActiveCount; + } + + public int getCurrentCorePoolSize() { + return currentCorePoolSize; + } + + public int getCurrentPoolSize() { + return currentPoolSize; + } + + public int getCurrentQueueSize() { + return currentQueueSize; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixUtilization.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixUtilization.java new file mode 100644 index 0000000..8cda449 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixUtilization.java @@ -0,0 +1,44 @@ +/** + * Copyright 2016 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.metric.sample; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixThreadPoolKey; + +import java.util.Map; + +public class HystrixUtilization { + private final Map commandUtilizationMap; + private final Map threadPoolUtilizationMap; + + public HystrixUtilization(Map commandUtilizationMap, Map threadPoolUtilizationMap) { + this.commandUtilizationMap = commandUtilizationMap; + this.threadPoolUtilizationMap = threadPoolUtilizationMap; + } + + public static HystrixUtilization from(Map commandUtilizationMap, + Map threadPoolUtilizationMap) { + return new HystrixUtilization(commandUtilizationMap, threadPoolUtilizationMap); + } + + public Map getCommandUtilizationMap() { + return commandUtilizationMap; + } + + public Map getThreadPoolUtilizationMap() { + return threadPoolUtilizationMap; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixUtilizationStream.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixUtilizationStream.java new file mode 100644 index 0000000..d509e4d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/metric/sample/HystrixUtilizationStream.java @@ -0,0 +1,165 @@ +/** + * Copyright 2016 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.metric.sample; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Func1; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class samples current Hystrix utilization of resources and exposes that as a stream + */ +public class HystrixUtilizationStream { + private final int intervalInMilliseconds; + private final Observable allUtilizationStream; + private final AtomicBoolean isSourceCurrentlySubscribed = new AtomicBoolean(false); + + private static final DynamicIntProperty dataEmissionIntervalInMs = + DynamicPropertyFactory.getInstance().getIntProperty("hystrix.stream.utilization.intervalInMilliseconds", 500); + + + private static final Func1 getAllUtilization = + new Func1() { + @Override + public HystrixUtilization call(Long timestamp) { + return HystrixUtilization.from( + getAllCommandUtilization.call(timestamp), + getAllThreadPoolUtilization.call(timestamp) + ); + } + }; + + /** + * @deprecated Not for public use. Please use {@link #getInstance()}. This facilitates better stream-sharing + * @param intervalInMilliseconds milliseconds between data emissions + */ + @Deprecated //deprecated in 1.5.4. + public HystrixUtilizationStream(final int intervalInMilliseconds) { + this.intervalInMilliseconds = intervalInMilliseconds; + this.allUtilizationStream = Observable.interval(intervalInMilliseconds, TimeUnit.MILLISECONDS) + .map(getAllUtilization) + .doOnSubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(true); + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + isSourceCurrentlySubscribed.set(false); + } + }) + .share() + .onBackpressureDrop(); + } + + //The data emission interval is looked up on startup only + private static final HystrixUtilizationStream INSTANCE = + new HystrixUtilizationStream(dataEmissionIntervalInMs.get()); + + public static HystrixUtilizationStream getInstance() { + return INSTANCE; + } + + static HystrixUtilizationStream getNonSingletonInstanceOnlyUsedInUnitTests(int delayInMs) { + return new HystrixUtilizationStream(delayInMs); + } + + /** + * Return a ref-counted stream that will only do work when at least one subscriber is present + */ + public Observable observe() { + return allUtilizationStream; + } + + public Observable> observeCommandUtilization() { + return allUtilizationStream.map(getOnlyCommandUtilization); + } + + public Observable> observeThreadPoolUtilization() { + return allUtilizationStream.map(getOnlyThreadPoolUtilization); + } + + public int getIntervalInMilliseconds() { + return this.intervalInMilliseconds; + } + + public boolean isSourceCurrentlySubscribed() { + return isSourceCurrentlySubscribed.get(); + } + + private static HystrixCommandUtilization sampleCommandUtilization(HystrixCommandMetrics commandMetrics) { + return HystrixCommandUtilization.sample(commandMetrics); + } + + private static HystrixThreadPoolUtilization sampleThreadPoolUtilization(HystrixThreadPoolMetrics threadPoolMetrics) { + return HystrixThreadPoolUtilization.sample(threadPoolMetrics); + } + + private static final Func1> getAllCommandUtilization = + new Func1>() { + @Override + public Map call(Long timestamp) { + Map commandUtilizationPerKey = new HashMap(); + for (HystrixCommandMetrics commandMetrics: HystrixCommandMetrics.getInstances()) { + HystrixCommandKey commandKey = commandMetrics.getCommandKey(); + commandUtilizationPerKey.put(commandKey, sampleCommandUtilization(commandMetrics)); + } + return commandUtilizationPerKey; + } + }; + + private static final Func1> getAllThreadPoolUtilization = + new Func1>() { + @Override + public Map call(Long timestamp) { + Map threadPoolUtilizationPerKey = new HashMap(); + for (HystrixThreadPoolMetrics threadPoolMetrics: HystrixThreadPoolMetrics.getInstances()) { + HystrixThreadPoolKey threadPoolKey = threadPoolMetrics.getThreadPoolKey(); + threadPoolUtilizationPerKey.put(threadPoolKey, sampleThreadPoolUtilization(threadPoolMetrics)); + } + return threadPoolUtilizationPerKey; + } + }; + + private static final Func1> getOnlyCommandUtilization = + new Func1>() { + @Override + public Map call(HystrixUtilization hystrixUtilization) { + return hystrixUtilization.getCommandUtilizationMap(); + } + }; + + private static final Func1> getOnlyThreadPoolUtilization = + new Func1>() { + @Override + public Map call(HystrixUtilization hystrixUtilization) { + return hystrixUtilization.getThreadPoolUtilizationMap(); + } + }; +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/package-info.java new file mode 100644 index 0000000..332eab0 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2012 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. + */ +/** + * Core functionality of Hystrix including the HystrixCommand and HystrixCollapser to be extended from. + * + * @since 1.0.0 + */ +package com.netflix.hystrix; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/HystrixArchaiusHelper.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/HystrixArchaiusHelper.java new file mode 100644 index 0000000..f4914df --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/HystrixArchaiusHelper.java @@ -0,0 +1,90 @@ +/** + * Copyright 2016 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.strategy; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import com.netflix.hystrix.strategy.properties.HystrixDynamicProperties; + +/** + * @ExcludeFromJavadoc + * @author agentgt + */ +class HystrixArchaiusHelper { + + /** + * To keep class loading minimal for those that have archaius in the classpath but choose not to use it. + * @ExcludeFromJavadoc + * @author agent + */ + private static class LazyHolder { + private final static Method loadCascadedPropertiesFromResources; + private final static String CONFIG_MANAGER_CLASS = "com.netflix.config.ConfigurationManager"; + + static { + Method load = null; + try { + Class configManager = Class.forName(CONFIG_MANAGER_CLASS); + load = configManager.getMethod("loadCascadedPropertiesFromResources", String.class); + } catch (Exception e) { + } + + loadCascadedPropertiesFromResources = load; + } + } + + /** + * @ExcludeFromJavadoc + */ + static boolean isArchaiusV1Available() { + return LazyHolder.loadCascadedPropertiesFromResources != null; + } + + static void loadCascadedPropertiesFromResources(String name) { + if (isArchaiusV1Available()) { + try { + LazyHolder.loadCascadedPropertiesFromResources.invoke(null, name); + } catch (IllegalAccessException e) { + } catch (IllegalArgumentException e) { + } catch (InvocationTargetException e) { + } + } + } + + /** + * @ExcludeFromJavadoc + */ + static HystrixDynamicProperties createArchaiusDynamicProperties() { + if (isArchaiusV1Available()) { + loadCascadedPropertiesFromResources("hystrix-plugins"); + try { + Class defaultProperties = Class.forName( + "com.netflix.hystrix.strategy.properties.archaius" + ".HystrixDynamicPropertiesArchaius"); + return (HystrixDynamicProperties) defaultProperties.newInstance(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + // Fallback to System properties. + return null; + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/HystrixPlugins.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/HystrixPlugins.java new file mode 100644 index 0000000..3aa0839 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/HystrixPlugins.java @@ -0,0 +1,414 @@ +/** + * Copyright 2012 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.strategy; + +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault; +import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; +import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifierDefault; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHookDefault; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherDefault; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherFactory; +import com.netflix.hystrix.strategy.properties.HystrixDynamicProperties; +import com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesSystemProperties; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategyDefault; + +/** + * Registry for plugin implementations that allows global override and handles the retrieval of correct implementation based on order of precedence: + *

    + *
  1. plugin registered globally via register methods in this class
  2. + *
  3. plugin registered and retrieved using the resolved {@link HystrixDynamicProperties} (usually Archaius, see get methods for property names)
  4. + *
  5. plugin registered and retrieved using the JDK {@link ServiceLoader}
  6. + *
  7. default implementation
  8. + *
+ * + * The exception to the above order is the {@link HystrixDynamicProperties} implementation + * which is only loaded through System.properties or the ServiceLoader (see the {@link HystrixPlugins#getDynamicProperties() getter} for more details). + *

+ * See the Hystrix GitHub Wiki for more information: https://github.com/Netflix/Hystrix/wiki/Plugins. + */ +public class HystrixPlugins { + + //We should not load unless we are requested to. This avoids accidental initialization. @agentgt + //See https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom + private static class LazyHolder { private static final HystrixPlugins INSTANCE = HystrixPlugins.create(); } + private final ClassLoader classLoader; + /* package */ final AtomicReference notifier = new AtomicReference(); + /* package */ final AtomicReference concurrencyStrategy = new AtomicReference(); + /* package */ final AtomicReference metricsPublisher = new AtomicReference(); + /* package */ final AtomicReference propertiesFactory = new AtomicReference(); + /* package */ final AtomicReference commandExecutionHook = new AtomicReference(); + private final HystrixDynamicProperties dynamicProperties; + + + private HystrixPlugins(ClassLoader classLoader, LoggerSupplier logSupplier) { + //This will load Archaius if its in the classpath. + this.classLoader = classLoader; + //N.B. Do not use a logger before this is loaded as it will most likely load the configuration system. + //The configuration system may need to do something prior to loading logging. @agentgt + dynamicProperties = resolveDynamicProperties(classLoader, logSupplier); + } + + /** + * For unit test purposes. + * @ExcludeFromJavadoc + */ + /* private */ static HystrixPlugins create(ClassLoader classLoader, LoggerSupplier logSupplier) { + return new HystrixPlugins(classLoader, logSupplier); + } + + /** + * For unit test purposes. + * @ExcludeFromJavadoc + */ + /* private */ static HystrixPlugins create(ClassLoader classLoader) { + return new HystrixPlugins(classLoader, new LoggerSupplier() { + @Override + public Logger getLogger() { + return LoggerFactory.getLogger(HystrixPlugins.class); + } + }); + } + /** + * @ExcludeFromJavadoc + */ + /* private */ static HystrixPlugins create() { + return create(HystrixPlugins.class.getClassLoader()); + } + + public static HystrixPlugins getInstance() { + return LazyHolder.INSTANCE; + } + + /** + * Reset all of the HystrixPlugins to null. You may invoke this directly, or it also gets invoked via Hystrix.reset() + */ + public static void reset() { + getInstance().notifier.set(null); + getInstance().concurrencyStrategy.set(null); + getInstance().metricsPublisher.set(null); + getInstance().propertiesFactory.set(null); + getInstance().commandExecutionHook.set(null); + HystrixMetricsPublisherFactory.reset(); + } + + /** + * Retrieve instance of {@link HystrixEventNotifier} to use based on order of precedence as defined in {@link HystrixPlugins} class header. + *

+ * Override default by using {@link #registerEventNotifier(HystrixEventNotifier)} or setting property (via Archaius): hystrix.plugin.HystrixEventNotifier.implementation with the full classname to + * load. + * + * @return {@link HystrixEventNotifier} implementation to use + */ + public HystrixEventNotifier getEventNotifier() { + if (notifier.get() == null) { + // check for an implementation from Archaius first + Object impl = getPluginImplementation(HystrixEventNotifier.class); + if (impl == null) { + // nothing set via Archaius so initialize with default + notifier.compareAndSet(null, HystrixEventNotifierDefault.getInstance()); + // we don't return from here but call get() again in case of thread-race so the winner will always get returned + } else { + // we received an implementation from Archaius so use it + notifier.compareAndSet(null, (HystrixEventNotifier) impl); + } + } + return notifier.get(); + } + + /** + * Register a {@link HystrixEventNotifier} implementation as a global override of any injected or default implementations. + * + * @param impl + * {@link HystrixEventNotifier} implementation + * @throws IllegalStateException + * if called more than once or after the default was initialized (if usage occurs before trying to register) + */ + public void registerEventNotifier(HystrixEventNotifier impl) { + if (!notifier.compareAndSet(null, impl)) { + throw new IllegalStateException("Another strategy was already registered."); + } + } + + /** + * Retrieve instance of {@link HystrixConcurrencyStrategy} to use based on order of precedence as defined in {@link HystrixPlugins} class header. + *

+ * Override default by using {@link #registerConcurrencyStrategy(HystrixConcurrencyStrategy)} or setting property (via Archaius): hystrix.plugin.HystrixConcurrencyStrategy.implementation with the + * full classname to load. + * + * @return {@link HystrixConcurrencyStrategy} implementation to use + */ + public HystrixConcurrencyStrategy getConcurrencyStrategy() { + if (concurrencyStrategy.get() == null) { + // check for an implementation from Archaius first + Object impl = getPluginImplementation(HystrixConcurrencyStrategy.class); + if (impl == null) { + // nothing set via Archaius so initialize with default + concurrencyStrategy.compareAndSet(null, HystrixConcurrencyStrategyDefault.getInstance()); + // we don't return from here but call get() again in case of thread-race so the winner will always get returned + } else { + // we received an implementation from Archaius so use it + concurrencyStrategy.compareAndSet(null, (HystrixConcurrencyStrategy) impl); + } + } + return concurrencyStrategy.get(); + } + + /** + * Register a {@link HystrixConcurrencyStrategy} implementation as a global override of any injected or default implementations. + * + * @param impl + * {@link HystrixConcurrencyStrategy} implementation + * @throws IllegalStateException + * if called more than once or after the default was initialized (if usage occurs before trying to register) + */ + public void registerConcurrencyStrategy(HystrixConcurrencyStrategy impl) { + if (!concurrencyStrategy.compareAndSet(null, impl)) { + throw new IllegalStateException("Another strategy was already registered."); + } + } + + /** + * Retrieve instance of {@link HystrixMetricsPublisher} to use based on order of precedence as defined in {@link HystrixPlugins} class header. + *

+ * Override default by using {@link #registerMetricsPublisher(HystrixMetricsPublisher)} or setting property (via Archaius): hystrix.plugin.HystrixMetricsPublisher.implementation with the full + * classname to load. + * + * @return {@link HystrixMetricsPublisher} implementation to use + */ + public HystrixMetricsPublisher getMetricsPublisher() { + if (metricsPublisher.get() == null) { + // check for an implementation from Archaius first + Object impl = getPluginImplementation(HystrixMetricsPublisher.class); + if (impl == null) { + // nothing set via Archaius so initialize with default + metricsPublisher.compareAndSet(null, HystrixMetricsPublisherDefault.getInstance()); + // we don't return from here but call get() again in case of thread-race so the winner will always get returned + } else { + // we received an implementation from Archaius so use it + metricsPublisher.compareAndSet(null, (HystrixMetricsPublisher) impl); + } + } + return metricsPublisher.get(); + } + + /** + * Register a {@link HystrixMetricsPublisher} implementation as a global override of any injected or default implementations. + * + * @param impl + * {@link HystrixMetricsPublisher} implementation + * @throws IllegalStateException + * if called more than once or after the default was initialized (if usage occurs before trying to register) + */ + public void registerMetricsPublisher(HystrixMetricsPublisher impl) { + if (!metricsPublisher.compareAndSet(null, impl)) { + throw new IllegalStateException("Another strategy was already registered."); + } + } + + /** + * Retrieve instance of {@link HystrixPropertiesStrategy} to use based on order of precedence as defined in {@link HystrixPlugins} class header. + *

+ * Override default by using {@link #registerPropertiesStrategy(HystrixPropertiesStrategy)} or setting property (via Archaius): hystrix.plugin.HystrixPropertiesStrategy.implementation with the full + * classname to load. + * + * @return {@link HystrixPropertiesStrategy} implementation to use + */ + public HystrixPropertiesStrategy getPropertiesStrategy() { + if (propertiesFactory.get() == null) { + // check for an implementation from Archaius first + Object impl = getPluginImplementation(HystrixPropertiesStrategy.class); + if (impl == null) { + // nothing set via Archaius so initialize with default + propertiesFactory.compareAndSet(null, HystrixPropertiesStrategyDefault.getInstance()); + // we don't return from here but call get() again in case of thread-race so the winner will always get returned + } else { + // we received an implementation from Archaius so use it + propertiesFactory.compareAndSet(null, (HystrixPropertiesStrategy) impl); + } + } + return propertiesFactory.get(); + } + + /** + * Retrieves the instance of {@link HystrixDynamicProperties} to use. + *

+ * Unlike the other plugins this plugin cannot be re-registered and is only loaded at creation + * of the {@link HystrixPlugins} singleton. + *

+ * The order of precedence for loading implementations is: + *

    + *
  1. System property of key: hystrix.plugin.HystrixDynamicProperties.implementation with the class as a value.
  2. + *
  3. The {@link ServiceLoader}.
  4. + *
  5. An implementation based on Archaius if it is found in the classpath is used.
  6. + *
  7. A fallback implementation based on the {@link System#getProperties()}
  8. + *
+ * @return never null + */ + public HystrixDynamicProperties getDynamicProperties() { + return dynamicProperties; + } + + /** + * Register a {@link HystrixPropertiesStrategy} implementation as a global override of any injected or default implementations. + * + * @param impl + * {@link HystrixPropertiesStrategy} implementation + * @throws IllegalStateException + * if called more than once or after the default was initialized (if usage occurs before trying to register) + */ + public void registerPropertiesStrategy(HystrixPropertiesStrategy impl) { + if (!propertiesFactory.compareAndSet(null, impl)) { + throw new IllegalStateException("Another strategy was already registered."); + } + } + + /** + * Retrieve instance of {@link HystrixCommandExecutionHook} to use based on order of precedence as defined in {@link HystrixPlugins} class header. + *

+ * Override default by using {@link #registerCommandExecutionHook(HystrixCommandExecutionHook)} or setting property (via Archaius): hystrix.plugin.HystrixCommandExecutionHook.implementation with the + * full classname to + * load. + * + * @return {@link HystrixCommandExecutionHook} implementation to use + * + * @since 1.2 + */ + public HystrixCommandExecutionHook getCommandExecutionHook() { + if (commandExecutionHook.get() == null) { + // check for an implementation from Archaius first + Object impl = getPluginImplementation(HystrixCommandExecutionHook.class); + if (impl == null) { + // nothing set via Archaius so initialize with default + commandExecutionHook.compareAndSet(null, HystrixCommandExecutionHookDefault.getInstance()); + // we don't return from here but call get() again in case of thread-race so the winner will always get returned + } else { + // we received an implementation from Archaius so use it + commandExecutionHook.compareAndSet(null, (HystrixCommandExecutionHook) impl); + } + } + return commandExecutionHook.get(); + } + + /** + * Register a {@link HystrixCommandExecutionHook} implementation as a global override of any injected or default implementations. + * + * @param impl + * {@link HystrixCommandExecutionHook} implementation + * @throws IllegalStateException + * if called more than once or after the default was initialized (if usage occurs before trying to register) + * + * @since 1.2 + */ + public void registerCommandExecutionHook(HystrixCommandExecutionHook impl) { + if (!commandExecutionHook.compareAndSet(null, impl)) { + throw new IllegalStateException("Another strategy was already registered."); + } + } + + + private T getPluginImplementation(Class pluginClass) { + T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties); + if (p != null) return p; + return findService(pluginClass, classLoader); + } + + @SuppressWarnings("unchecked") + private static T getPluginImplementationViaProperties(Class pluginClass, HystrixDynamicProperties dynamicProperties) { + String classSimpleName = pluginClass.getSimpleName(); + // Check Archaius for plugin class. + String propertyName = "hystrix.plugin." + classSimpleName + ".implementation"; + String implementingClass = dynamicProperties.getString(propertyName, null).get(); + if (implementingClass != null) { + try { + Class cls = Class.forName(implementingClass); + // narrow the scope (cast) to the type we're expecting + cls = cls.asSubclass(pluginClass); + return (T) cls.newInstance(); + } catch (ClassCastException e) { + throw new RuntimeException(classSimpleName + " implementation is not an instance of " + classSimpleName + ": " + implementingClass); + } catch (ClassNotFoundException e) { + throw new RuntimeException(classSimpleName + " implementation class not found: " + implementingClass, e); + } catch (InstantiationException e) { + throw new RuntimeException(classSimpleName + " implementation not able to be instantiated: " + implementingClass, e); + } catch (IllegalAccessException e) { + throw new RuntimeException(classSimpleName + " implementation not able to be accessed: " + implementingClass, e); + } + } else { + return null; + } + } + + + + private static HystrixDynamicProperties resolveDynamicProperties(ClassLoader classLoader, LoggerSupplier logSupplier) { + HystrixDynamicProperties hp = getPluginImplementationViaProperties(HystrixDynamicProperties.class, + HystrixDynamicPropertiesSystemProperties.getInstance()); + if (hp != null) { + logSupplier.getLogger().debug( + "Created HystrixDynamicProperties instance from System property named " + + "\"hystrix.plugin.HystrixDynamicProperties.implementation\". Using class: {}", + hp.getClass().getCanonicalName()); + return hp; + } + hp = findService(HystrixDynamicProperties.class, classLoader); + if (hp != null) { + logSupplier.getLogger() + .debug("Created HystrixDynamicProperties instance by loading from ServiceLoader. Using class: {}", + hp.getClass().getCanonicalName()); + return hp; + } + hp = HystrixArchaiusHelper.createArchaiusDynamicProperties(); + if (hp != null) { + logSupplier.getLogger().debug("Created HystrixDynamicProperties. Using class : {}", + hp.getClass().getCanonicalName()); + return hp; + } + hp = HystrixDynamicPropertiesSystemProperties.getInstance(); + logSupplier.getLogger().info("Using System Properties for HystrixDynamicProperties! Using class: {}", + hp.getClass().getCanonicalName()); + return hp; + } + + private static T findService( + Class spi, + ClassLoader classLoader) throws ServiceConfigurationError { + + ServiceLoader sl = ServiceLoader.load(spi, + classLoader); + for (T s : sl) { + if (s != null) + return s; + } + return null; + } + + interface LoggerSupplier { + Logger getLogger(); + } + + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategy.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategy.java new file mode 100644 index 0000000..d5ce887 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategy.java @@ -0,0 +1,201 @@ +/** + * Copyright 2013 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.strategy.concurrency; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.PlatformSpecific; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Abstract class for defining different behavior or implementations for concurrency related aspects of the system with default implementations. + *

+ * For example, every {@link Callable} executed by {@link HystrixCommand} will call {@link #wrapCallable(Callable)} to give a chance for custom implementations to decorate the {@link Callable} with + * additional behavior. + *

+ * When you implement a concrete {@link HystrixConcurrencyStrategy}, you should make the strategy idempotent w.r.t ThreadLocals. + * Since the usage of threads by Hystrix is internal, Hystrix does not attempt to apply the strategy in an idempotent way. + * Instead, you should write your strategy to work idempotently. See https://github.com/Netflix/Hystrix/issues/351 for a more detailed discussion. + *

+ * See {@link HystrixPlugins} or the Hystrix GitHub Wiki for information on configuring plugins: https://github.com/Netflix/Hystrix/wiki/Plugins. + */ +public abstract class HystrixConcurrencyStrategy { + + private final static Logger logger = LoggerFactory.getLogger(HystrixConcurrencyStrategy.class); + + /** + * Factory method to provide {@link ThreadPoolExecutor} instances as desired. + *

+ * Note that the corePoolSize, maximumPoolSize and keepAliveTime values will be dynamically set during runtime if their values change using the {@link ThreadPoolExecutor#setCorePoolSize}, + * {@link ThreadPoolExecutor#setMaximumPoolSize} and {@link ThreadPoolExecutor#setKeepAliveTime} methods. + *

+ * Default Implementation + *

+ * Implementation using standard java.util.concurrent.ThreadPoolExecutor + * + * @param threadPoolKey + * {@link HystrixThreadPoolKey} representing the {@link HystrixThreadPool} that this {@link ThreadPoolExecutor} will be used for. + * @param corePoolSize + * Core number of threads requested via properties (or system default if no properties set). + * @param maximumPoolSize + * Max number of threads requested via properties (or system default if no properties set). + * @param keepAliveTime + * Keep-alive time for threads requested via properties (or system default if no properties set). + * @param unit + * {@link TimeUnit} corresponding with keepAliveTime + * @param workQueue + * {@code BlockingQueue} as provided by {@link #getBlockingQueue(int)} + * @return instance of {@link ThreadPoolExecutor} + */ + public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixProperty corePoolSize, HystrixProperty maximumPoolSize, HystrixProperty keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { + final ThreadFactory threadFactory = getThreadFactory(threadPoolKey); + + final int dynamicCoreSize = corePoolSize.get(); + final int dynamicMaximumSize = maximumPoolSize.get(); + + if (dynamicCoreSize > dynamicMaximumSize) { + logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " + + dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ". Maximum size will be set to " + + dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value"); + return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime.get(), unit, workQueue, threadFactory); + } else { + return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime.get(), unit, workQueue, threadFactory); + } + } + + public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { + final ThreadFactory threadFactory = getThreadFactory(threadPoolKey); + + final boolean allowMaximumSizeToDivergeFromCoreSize = threadPoolProperties.getAllowMaximumSizeToDivergeFromCoreSize().get(); + final int dynamicCoreSize = threadPoolProperties.coreSize().get(); + final int keepAliveTime = threadPoolProperties.keepAliveTimeMinutes().get(); + final int maxQueueSize = threadPoolProperties.maxQueueSize().get(); + final BlockingQueue workQueue = getBlockingQueue(maxQueueSize); + + if (allowMaximumSizeToDivergeFromCoreSize) { + final int dynamicMaximumSize = threadPoolProperties.maximumSize().get(); + if (dynamicCoreSize > dynamicMaximumSize) { + logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " + + dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ". Maximum size will be set to " + + dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value"); + return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory); + } else { + return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory); + } + } else { + return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory); + } + } + + private static ThreadFactory getThreadFactory(final HystrixThreadPoolKey threadPoolKey) { + if (!PlatformSpecific.isAppEngineStandardEnvironment()) { + return new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "hystrix-" + threadPoolKey.name() + "-" + threadNumber.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + + }; + } else { + return PlatformSpecific.getAppEngineThreadFactory(); + } + } + + /** + * Factory method to provide instance of {@code BlockingQueue} used for each {@link ThreadPoolExecutor} as constructed in {@link #getThreadPool}. + *

+ * Note: The maxQueueSize value is provided so any type of queue can be used but typically an implementation such as {@link SynchronousQueue} without a queue (just a handoff) is preferred as + * queueing is an anti-pattern to be purposefully avoided for latency tolerance reasons. + *

+ * Default Implementation + *

+ * Implementation returns {@link SynchronousQueue} when maxQueueSize <= 0 or {@link LinkedBlockingQueue} when maxQueueSize > 0. + * + * @param maxQueueSize + * The max size of the queue requested via properties (or system default if no properties set). + * @return instance of {@code BlockingQueue} + */ + public BlockingQueue getBlockingQueue(int maxQueueSize) { + /* + * We are using SynchronousQueue if maxQueueSize <= 0 (meaning a queue is not wanted). + *

+ * SynchronousQueue will do a handoff from calling thread to worker thread and not allow queuing which is what we want. + *

+ * Queuing results in added latency and would only occur when the thread-pool is full at which point there are latency issues + * and rejecting is the preferred solution. + */ + if (maxQueueSize <= 0) { + return new SynchronousQueue(); + } else { + return new LinkedBlockingQueue(maxQueueSize); + } + } + + /** + * Provides an opportunity to wrap/decorate a {@code Callable} before execution. + *

+ * This can be used to inject additional behavior such as copying of thread state (such as {@link ThreadLocal}). + *

+ * Default Implementation + *

+ * Pass-thru that does no wrapping. + * + * @param callable + * {@code Callable} to be executed via a {@link ThreadPoolExecutor} + * @return {@code Callable} either as a pass-thru or wrapping the one given + */ + public Callable wrapCallable(Callable callable) { + return callable; + } + + /** + * Factory method to return an implementation of {@link HystrixRequestVariable} that behaves like a {@link ThreadLocal} except that it + * is scoped to a request instead of a thread. + *

+ * For example, if a request starts with an HTTP request and ends with the HTTP response, then {@link HystrixRequestVariable} should + * be initialized at the beginning, available on any and all threads spawned during the request and then cleaned up once the HTTP request is completed. + *

+ * If this method is implemented it is generally necessary to also implemented {@link #wrapCallable(Callable)} in order to copy state + * from parent to child thread. + * + * @param rv + * {@link HystrixRequestVariableLifecycle} with lifecycle implementations from Hystrix + * @return {@code HystrixRequestVariable} + */ + public HystrixRequestVariable getRequestVariable(final HystrixRequestVariableLifecycle rv) { + return new HystrixLifecycleForwardingRequestVariable(rv); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyDefault.java new file mode 100644 index 0000000..a9d1b6a --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyDefault.java @@ -0,0 +1,34 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +/** + * Default implementation of {@link HystrixConcurrencyStrategy} using standard java.util.concurrent.* implementations. + * + * @ExcludeFromJavadoc + */ +public class HystrixConcurrencyStrategyDefault extends HystrixConcurrencyStrategy { + + private static HystrixConcurrencyStrategyDefault INSTANCE = new HystrixConcurrencyStrategyDefault(); + + public static HystrixConcurrencyStrategy getInstance() { + return INSTANCE; + } + + private HystrixConcurrencyStrategyDefault() { + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContexSchedulerAction.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContexSchedulerAction.java new file mode 100644 index 0000000..17d4239 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContexSchedulerAction.java @@ -0,0 +1,75 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +import java.util.concurrent.Callable; + +import rx.functions.Action0; +import rx.functions.Func2; + +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Wrapper around {@link Func2} that manages the {@link HystrixRequestContext} initialization and cleanup for the execution of the {@link Func2} + * + * @param + * Return type of {@link Func2} + * + * @ExcludeFromJavadoc + */ +public class HystrixContexSchedulerAction implements Action0 { + + private final Action0 actual; + private final HystrixRequestContext parentThreadState; + private final Callable c; + + public HystrixContexSchedulerAction(Action0 action) { + this(HystrixPlugins.getInstance().getConcurrencyStrategy(), action); + } + + public HystrixContexSchedulerAction(final HystrixConcurrencyStrategy concurrencyStrategy, Action0 action) { + this.actual = action; + this.parentThreadState = HystrixRequestContext.getContextForCurrentThread(); + + this.c = concurrencyStrategy.wrapCallable(new Callable() { + + @Override + public Void call() throws Exception { + HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread(); + try { + // set the state of this thread to that of its parent + HystrixRequestContext.setContextOnCurrentThread(parentThreadState); + // execute actual Action0 with the state of the parent + actual.call(); + return null; + } finally { + // restore this thread back to its original state + HystrixRequestContext.setContextOnCurrentThread(existingState); + } + } + }); + } + + @Override + public void call() { + try { + c.call(); + } catch (Exception e) { + throw new RuntimeException("Failed executing wrapped Action0", e); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextCallable.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextCallable.java new file mode 100644 index 0000000..35ff1c8 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextCallable.java @@ -0,0 +1,58 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +import java.util.concurrent.Callable; + +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Wrapper around {@link Callable} that manages the {@link HystrixRequestContext} initialization and cleanup for the execution of the {@link Callable} + * + * @param + * Return type of {@link Callable} + * + * @ExcludeFromJavadoc + */ +public class HystrixContextCallable implements Callable { + + private final Callable actual; + private final HystrixRequestContext parentThreadState; + + public HystrixContextCallable(Callable actual) { + this(HystrixPlugins.getInstance().getConcurrencyStrategy(), actual); + } + + public HystrixContextCallable(HystrixConcurrencyStrategy concurrencyStrategy, Callable actual) { + this.actual = concurrencyStrategy.wrapCallable(actual); + this.parentThreadState = HystrixRequestContext.getContextForCurrentThread(); + } + + @Override + public K call() throws Exception { + HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread(); + try { + // set the state of this thread to that of its parent + HystrixRequestContext.setContextOnCurrentThread(parentThreadState); + // execute actual Callable with the state of the parent + return actual.call(); + } finally { + // restore this thread back to its original state + HystrixRequestContext.setContextOnCurrentThread(existingState); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextRunnable.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextRunnable.java new file mode 100644 index 0000000..b95a517 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextRunnable.java @@ -0,0 +1,71 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +import java.util.concurrent.Callable; + +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Wrapper around {@link Runnable} that manages the {@link HystrixRequestContext} initialization and cleanup for the execution of the {@link Runnable} + * + * @ExcludeFromJavadoc + */ +public class HystrixContextRunnable implements Runnable { + + private final Callable actual; + private final HystrixRequestContext parentThreadState; + + public HystrixContextRunnable(Runnable actual) { + this(HystrixPlugins.getInstance().getConcurrencyStrategy(), actual); + } + + public HystrixContextRunnable(HystrixConcurrencyStrategy concurrencyStrategy, final Runnable actual) { + this(concurrencyStrategy, HystrixRequestContext.getContextForCurrentThread(), actual); + } + + public HystrixContextRunnable(final HystrixConcurrencyStrategy concurrencyStrategy, final HystrixRequestContext hystrixRequestContext, final Runnable actual) { + this.actual = concurrencyStrategy.wrapCallable(new Callable() { + + @Override + public Void call() throws Exception { + actual.run(); + return null; + } + + }); + this.parentThreadState = hystrixRequestContext; + } + + @Override + public void run() { + HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread(); + try { + // set the state of this thread to that of its parent + HystrixRequestContext.setContextOnCurrentThread(parentThreadState); + // execute actual Callable with the state of the parent + try { + actual.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } finally { + // restore this thread back to its original state + HystrixRequestContext.setContextOnCurrentThread(existingState); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextScheduler.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextScheduler.java new file mode 100644 index 0000000..8ef177c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextScheduler.java @@ -0,0 +1,214 @@ +/** + * Copyright 2013 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.strategy.concurrency; + +import java.util.concurrent.*; + +import rx.*; +import rx.functions.Action0; +import rx.functions.Func0; +import rx.internal.schedulers.ScheduledAction; +import rx.subscriptions.*; + +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Wrap a {@link Scheduler} so that scheduled actions are wrapped with {@link HystrixContexSchedulerAction} so that + * the {@link HystrixRequestContext} is properly copied across threads (if they are used by the {@link Scheduler}). + */ +public class HystrixContextScheduler extends Scheduler { + + private final HystrixConcurrencyStrategy concurrencyStrategy; + private final Scheduler actualScheduler; + private final HystrixThreadPool threadPool; + + public HystrixContextScheduler(Scheduler scheduler) { + this.actualScheduler = scheduler; + this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy(); + this.threadPool = null; + } + + public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, Scheduler scheduler) { + this.actualScheduler = scheduler; + this.concurrencyStrategy = concurrencyStrategy; + this.threadPool = null; + } + + public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, HystrixThreadPool threadPool) { + this(concurrencyStrategy, threadPool, new Func0() { + @Override + public Boolean call() { + return true; + } + }); + } + + public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, HystrixThreadPool threadPool, Func0 shouldInterruptThread) { + this.concurrencyStrategy = concurrencyStrategy; + this.threadPool = threadPool; + this.actualScheduler = new ThreadPoolScheduler(threadPool, shouldInterruptThread); + } + + @Override + public Worker createWorker() { + return new HystrixContextSchedulerWorker(actualScheduler.createWorker()); + } + + private class HystrixContextSchedulerWorker extends Worker { + + private final Worker worker; + + private HystrixContextSchedulerWorker(Worker actualWorker) { + this.worker = actualWorker; + } + + @Override + public void unsubscribe() { + worker.unsubscribe(); + } + + @Override + public boolean isUnsubscribed() { + return worker.isUnsubscribed(); + } + + @Override + public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { + if (threadPool != null) { + if (!threadPool.isQueueSpaceAvailable()) { + throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold."); + } + } + return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action), delayTime, unit); + } + + @Override + public Subscription schedule(Action0 action) { + if (threadPool != null) { + if (!threadPool.isQueueSpaceAvailable()) { + throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold."); + } + } + return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action)); + } + + } + + private static class ThreadPoolScheduler extends Scheduler { + + private final HystrixThreadPool threadPool; + private final Func0 shouldInterruptThread; + + public ThreadPoolScheduler(HystrixThreadPool threadPool, Func0 shouldInterruptThread) { + this.threadPool = threadPool; + this.shouldInterruptThread = shouldInterruptThread; + } + + @Override + public Worker createWorker() { + return new ThreadPoolWorker(threadPool, shouldInterruptThread); + } + + } + + /** + * Purely for scheduling work on a thread-pool. + *

+ * This is not natively supported by RxJava as of 0.18.0 because thread-pools + * are contrary to sequential execution. + *

+ * For the Hystrix case, each Command invocation has a single action so the concurrency + * issue is not a problem. + */ + private static class ThreadPoolWorker extends Worker { + + private final HystrixThreadPool threadPool; + private final CompositeSubscription subscription = new CompositeSubscription(); + private final Func0 shouldInterruptThread; + + public ThreadPoolWorker(HystrixThreadPool threadPool, Func0 shouldInterruptThread) { + this.threadPool = threadPool; + this.shouldInterruptThread = shouldInterruptThread; + } + + @Override + public void unsubscribe() { + subscription.unsubscribe(); + } + + @Override + public boolean isUnsubscribed() { + return subscription.isUnsubscribed(); + } + + @Override + public Subscription schedule(final Action0 action) { + if (subscription.isUnsubscribed()) { + // don't schedule, we are unsubscribed + return Subscriptions.unsubscribed(); + } + + // This is internal RxJava API but it is too useful. + ScheduledAction sa = new ScheduledAction(action); + + subscription.add(sa); + sa.addParent(subscription); + + ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPool.getExecutor(); + FutureTask f = (FutureTask) executor.submit(sa); + sa.add(new FutureCompleterWithConfigurableInterrupt(f, shouldInterruptThread, executor)); + + return sa; + } + + @Override + public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { + throw new IllegalStateException("Hystrix does not support delayed scheduling"); + } + } + + /** + * Very similar to rx.internal.schedulers.ScheduledAction.FutureCompleter, but with configurable interrupt behavior + */ + private static class FutureCompleterWithConfigurableInterrupt implements Subscription { + private final FutureTask f; + private final Func0 shouldInterruptThread; + private final ThreadPoolExecutor executor; + + private FutureCompleterWithConfigurableInterrupt(FutureTask f, Func0 shouldInterruptThread, ThreadPoolExecutor executor) { + this.f = f; + this.shouldInterruptThread = shouldInterruptThread; + this.executor = executor; + } + + @Override + public void unsubscribe() { + executor.remove(f); + if (shouldInterruptThread.call()) { + f.cancel(true); + } else { + f.cancel(false); + } + } + + @Override + public boolean isUnsubscribed() { + return f.isCancelled(); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixLifecycleForwardingRequestVariable.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixLifecycleForwardingRequestVariable.java new file mode 100644 index 0000000..49c4810 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixLifecycleForwardingRequestVariable.java @@ -0,0 +1,72 @@ +/** + * 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.strategy.concurrency; + +/** + * Implementation of {@link HystrixRequestVariable} which forwards to the wrapped + * {@link HystrixRequestVariableLifecycle}. + *

+ * This implementation also returns null when {@link #get()} is called while the {@link HystrixRequestContext} has not + * been initialized rather than throwing an exception, allowing for use in a {@link HystrixConcurrencyStrategy} which + * does not depend on an a HystrixRequestContext + */ +public class HystrixLifecycleForwardingRequestVariable extends HystrixRequestVariableDefault { + private final HystrixRequestVariableLifecycle lifecycle; + + /** + * Creates a HystrixRequestVariable which will return data as provided by the {@link HystrixRequestVariableLifecycle} + * @param lifecycle lifecycle used to provide values. Must have the same type parameter as the constructed instance. + */ + public HystrixLifecycleForwardingRequestVariable(HystrixRequestVariableLifecycle lifecycle) { + this.lifecycle = lifecycle; + } + + /** + * Delegates to the wrapped {@link HystrixRequestVariableLifecycle} + * @return T with initial value or null if none. + */ + @Override + public T initialValue() { + return lifecycle.initialValue(); + } + + /** + * Delegates to the wrapped {@link HystrixRequestVariableLifecycle} + * @param value + * of request variable to allow cleanup activity. + *

+ * If nothing needs to be cleaned up then nothing needs to be done in this method. + */ + @Override + public void shutdown(T value) { + lifecycle.shutdown(value); + } + + /** + * Return null if the {@link HystrixRequestContext} has not been initialized for the current thread. + *

+ * If {@link HystrixRequestContext} has been initialized then call method in superclass: + * {@link HystrixRequestVariableDefault#get()} + */ + @Override + public T get() { + if (!HystrixRequestContext.isCurrentThreadInitialized()) { + return null; + } + return super.get(); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestContext.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestContext.java new file mode 100644 index 0000000..c4eba3b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestContext.java @@ -0,0 +1,158 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +import java.io.Closeable; +import java.util.concurrent.ConcurrentHashMap; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixRequestCache; +import com.netflix.hystrix.HystrixRequestLog; + +/** + * Contains the state and manages the lifecycle of {@link HystrixRequestVariableDefault} objects that provide request scoped (rather than only thread scoped) variables so that multiple threads within + * a + * single request can share state: + *

    + *
  • request scoped caching as in {@link HystrixRequestCache} for de-duping {@link HystrixCommand} executions
  • + *
  • request scoped log of all events as in {@link HystrixRequestLog}
  • + *
  • automated batching of {@link HystrixCommand} executions within the scope of a request as in {@link HystrixCollapser}
  • + *
+ *

+ * If those features are not used then this does not need to be used. If those features are used then this must be initialized or a custom implementation of {@link HystrixRequestVariable} must be + * returned from {@link HystrixConcurrencyStrategy#getRequestVariable}. + *

+ * If {@link HystrixRequestVariableDefault} is used (directly or indirectly by above-mentioned features) and this context has not been initialized then an {@link IllegalStateException} will be thrown + * with a + * message such as:

HystrixRequestContext.initializeContext() must be called at the beginning of each request before RequestVariable functionality can be used.
+ *

+ * Example ServletFilter for initializing {@link HystrixRequestContext} at the beginning of an HTTP request and shutting down at the end: + * + *

+ * + *
+ * public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ *      HystrixRequestContext context = HystrixRequestContext.initializeContext();
+ *      try {
+ *           chain.doFilter(request, response);
+ *      } finally {
+ *           context.shutdown();
+ *      }
+ * }
+ * 
+ * + *
+ *

+ * You can find an implementation at hystrix-contrib/hystrix-request-servlet on GitHub. + *

+ * NOTE: If initializeContext() is called then shutdown() must also be called or a memory leak will occur. + */ +public class HystrixRequestContext implements Closeable { + + /* + * ThreadLocal on each thread will hold the HystrixRequestVariableState. + * + * Shutdown will clear the state inside HystrixRequestContext but not nullify the ThreadLocal on all + * child threads as these threads will not be known by the parent when cleanupAfterRequest() is called. + * + * However, the only thing held by those child threads until they are re-used and re-initialized is an empty + * HystrixRequestContext object with the ConcurrentHashMap within it nulled out since once it is nullified + * from the parent thread it is shared across all child threads. + */ + private static ThreadLocal requestVariables = new ThreadLocal(); + + public static boolean isCurrentThreadInitialized() { + HystrixRequestContext context = requestVariables.get(); + return context != null && context.state != null; + } + + public static HystrixRequestContext getContextForCurrentThread() { + HystrixRequestContext context = requestVariables.get(); + if (context != null && context.state != null) { + // context.state can be null when context is not null + // if a thread is being re-used and held a context previously, the context was shut down + // but the thread was not cleared + return context; + } else { + return null; + } + } + + public static void setContextOnCurrentThread(HystrixRequestContext state) { + requestVariables.set(state); + } + + /** + * Call this at the beginning of each request (from parent thread) + * to initialize the underlying context so that {@link HystrixRequestVariableDefault} can be used on any children threads and be accessible from + * the parent thread. + *

+ * NOTE: If this method is called then shutdown() must also be called or a memory leak will occur. + *

+ * See class header JavaDoc for example Servlet Filter implementation that initializes and shuts down the context. + */ + public static HystrixRequestContext initializeContext() { + HystrixRequestContext state = new HystrixRequestContext(); + requestVariables.set(state); + return state; + } + + /* + * This ConcurrentHashMap should not be made publicly accessible. It is the state of RequestVariables for a given RequestContext. + * + * Only HystrixRequestVariable has a reason to be accessing this field. + */ + /* package */ConcurrentHashMap, HystrixRequestVariableDefault.LazyInitializer> state = new ConcurrentHashMap, HystrixRequestVariableDefault.LazyInitializer>(); + + // instantiation should occur via static factory methods. + private HystrixRequestContext() { + + } + + /** + * Shutdown {@link HystrixRequestVariableDefault} objects in this context. + *

+ * NOTE: This must be called if initializeContext() was called or a memory leak will occur. + */ + public void shutdown() { + if (state != null) { + for (HystrixRequestVariableDefault v : state.keySet()) { + // for each RequestVariable we call 'remove' which performs the shutdown logic + try { + HystrixRequestVariableDefault.remove(this, v); + } catch (Throwable t) { + HystrixRequestVariableDefault.logger.error("Error in shutdown, will continue with shutdown of other variables", t); + } + } + // null out so it can be garbage collected even if the containing object is still + // being held in ThreadLocals on threads that weren't cleaned up + state = null; + } + } + + /** + * Shutdown {@link HystrixRequestVariableDefault} objects in this context. + *

+ * NOTE: This must be called if initializeContext() was called or a memory leak will occur. + * + * This method invokes shutdown() + */ + public void close() { + shutdown(); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariable.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariable.java new file mode 100644 index 0000000..66cf292 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariable.java @@ -0,0 +1,41 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Interface for a variable similar to {@link ThreadLocal} but scoped at the user request level. + *

+ * Default implementation is {@link HystrixRequestVariableDefault} managed by {@link HystrixRequestContext}. + *

+ * Custom implementations can be injected using {@link HystrixPlugins} and {@link HystrixConcurrencyStrategy#getRequestVariable}. + *

+ * See JavaDoc of {@link HystrixRequestContext} for more information about functionality this enables and how to use the default implementation. + * + * @param + * Type to be stored on the HystrixRequestVariable + */ +public interface HystrixRequestVariable extends HystrixRequestVariableLifecycle { + + /** + * Retrieve current value or initialize and then return value for this variable for the current request scope. + * + * @return T value of variable for current request scope. + */ + public T get(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableDefault.java new file mode 100644 index 0000000..72f92b1 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableDefault.java @@ -0,0 +1,218 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default implementation of {@link HystrixRequestVariable}. Similar to {@link ThreadLocal} but scoped at the user request level. Context is managed via {@link HystrixRequestContext}. + *

+ * All statements below assume that child threads are spawned and initialized with the use of {@link HystrixContextCallable} or {@link HystrixContextRunnable} which capture state from a parent thread + * and propagate to the child thread. + *

+ * Characteristics that differ from ThreadLocal: + *

    + *
  • HystrixRequestVariable context must be initialized at the beginning of every request by {@link HystrixRequestContext#initializeContext}
  • + *
  • HystrixRequestVariables attached to a thread will be cleared at the end of every user request by {@link HystrixRequestContext#shutdown} which execute {@link #remove} for each + * HystrixRequestVariable
  • + *
  • HystrixRequestVariables have a {@link #shutdown} lifecycle method that gets called at the end of every user request (invoked when {@link HystrixRequestContext#shutdown} is called) to allow for + * resource cleanup.
  • + *
  • HystrixRequestVariables are copied (by reference) to child threads via the {@link HystrixRequestContext#getContextForCurrentThread} and {@link HystrixRequestContext#setContextOnCurrentThread} + * functionality.
  • + *
  • HystrixRequestVariables created on a child thread are available on sibling and parent threads.
  • + *
  • HystrixRequestVariables created on a child thread will be cleaned up by the parent thread via the {@link #shutdown} method.
  • + *
+ * + *

+ * Note on thread-safety: By design a HystrixRequestVariables is intended to be accessed by all threads in a user request, thus anything stored in a HystrixRequestVariables must be thread-safe and + * plan on being accessed/mutated concurrently. + *

+ * For example, a HashMap would likely not be a good choice for a RequestVariable value, but ConcurrentHashMap would. + * + * @param + * Type to be stored on the HystrixRequestVariable + *

+ * Example 1: {@code HystrixRequestVariable>}

+ * Example 2: {@code HystrixRequestVariable} + * + * @ExcludeFromJavadoc + * @ThreadSafe + */ +public class HystrixRequestVariableDefault implements HystrixRequestVariable { + static final Logger logger = LoggerFactory.getLogger(HystrixRequestVariableDefault.class); + + /** + * Creates a new HystrixRequestVariable that will exist across all threads + * within a {@link HystrixRequestContext} + */ + public HystrixRequestVariableDefault() { + } + + /** + * Get the current value for this variable for the current request context. + * + * @return the value of the variable for the current request, + * or null if no value has been set and there is no initial value + */ + @SuppressWarnings("unchecked") + public T get() { + if (HystrixRequestContext.getContextForCurrentThread() == null) { + throw new IllegalStateException(HystrixRequestContext.class.getSimpleName() + ".initializeContext() must be called at the beginning of each request before RequestVariable functionality can be used."); + } + ConcurrentHashMap, LazyInitializer> variableMap = HystrixRequestContext.getContextForCurrentThread().state; + + // short-circuit the synchronized path below if we already have the value in the ConcurrentHashMap + LazyInitializer v = variableMap.get(this); + if (v != null) { + return (T) v.get(); + } + + /* + * Optimistically create a LazyInitializer to put into the ConcurrentHashMap. + * + * The LazyInitializer will not invoke initialValue() unless the get() method is invoked + * so we can optimistically instantiate LazyInitializer and then discard for garbage collection + * if the putIfAbsent fails. + * + * Whichever instance of LazyInitializer succeeds will then have get() invoked which will call + * the initialValue() method once-and-only-once. + */ + LazyInitializer l = new LazyInitializer(this); + LazyInitializer existing = variableMap.putIfAbsent(this, l); + if (existing == null) { + /* + * We won the thread-race so can use 'l' that we just created. + */ + return l.get(); + } else { + /* + * We lost the thread-race so let 'l' be garbage collected and instead return 'existing' + */ + return (T) existing.get(); + } + } + + /** + * Computes the initial value of the HystrixRequestVariable in a request. + *

+ * This is called the first time the value of the HystrixRequestVariable is fetched in a request. Override this to provide an initial value for a HystrixRequestVariable on each request on which it + * is used. + * + * The default implementation returns null. + * + * @return initial value of the HystrixRequestVariable to use for the instance being constructed + */ + public T initialValue() { + return null; + } + + /** + * Sets the value of the HystrixRequestVariable for the current request context. + *

+ * Note, if a value already exists, the set will result in overwriting that value. It is up to the caller to ensure the existing value is cleaned up. The {@link #shutdown} method will not be + * called + * + * @param value + * the value to set + */ + public void set(T value) { + HystrixRequestContext.getContextForCurrentThread().state.put(this, new LazyInitializer(this, value)); + } + + /** + * Removes the value of the HystrixRequestVariable from the current request. + *

+ * This will invoke {@link #shutdown} if implemented. + *

+ * If the value is subsequently fetched in the thread, the {@link #initialValue} method will be called again. + */ + public void remove() { + if (HystrixRequestContext.getContextForCurrentThread() != null) { + remove(HystrixRequestContext.getContextForCurrentThread(), this); + } + } + + @SuppressWarnings("unchecked") + /* package */static void remove(HystrixRequestContext context, HystrixRequestVariableDefault v) { + // remove first so no other threads get it + LazyInitializer o = context.state.remove(v); + if (o != null) { + // this thread removed it so let's execute shutdown + v.shutdown((T) o.get()); + } + } + + /** + * Provide life-cycle hook for a HystrixRequestVariable implementation to perform cleanup + * before the HystrixRequestVariable is removed from the current thread. + *

+ * This is executed at the end of each user request when {@link HystrixRequestContext#shutdown} is called or whenever {@link #remove} is invoked. + *

+ * By default does nothing. + *

+ * NOTE: Do not call get() from within this method or initialValue() will be invoked again. The current value is passed in as an argument. + * + * @param value + * the value of the HystrixRequestVariable being removed + */ + public void shutdown(T value) { + // do nothing by default + } + + /** + * Holder for a value that can be derived from the {@link HystrixRequestVariableDefault#initialValue} method that needs + * to be executed once-and-only-once. + *

+ * This class can be instantiated and garbage collected without calling initialValue() as long as the get() method is not invoked and can thus be used with compareAndSet in + * ConcurrentHashMap.putIfAbsent and allow "losers" in a thread-race to be discarded. + * + * @param + */ + /* package */static final class LazyInitializer { + // @GuardedBy("synchronization on get() or construction") + private T value; + + /* + * Boolean to ensure only-once initialValue() execution instead of using + * a null check in case initialValue() returns null + */ + // @GuardedBy("synchronization on get() or construction") + private boolean initialized = false; + + private final HystrixRequestVariableDefault rv; + + private LazyInitializer(HystrixRequestVariableDefault rv) { + this.rv = rv; + } + + private LazyInitializer(HystrixRequestVariableDefault rv, T value) { + this.rv = rv; + this.value = value; + this.initialized = true; + } + + public synchronized T get() { + if (!initialized) { + value = rv.initialValue(); + initialized = true; + } + return value; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableHolder.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableHolder.java new file mode 100644 index 0000000..84bd70d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableHolder.java @@ -0,0 +1,113 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory that encompasses functionality of {@link HystrixRequestVariable} for internal Hystrix code. + *

+ * This is used as a layer between the actual {@link HystrixRequestVariable} and calling code to allow injected implementations of {@link HystrixConcurrencyStrategy}. + *

+ * Typically a {@link HystrixRequestVariable} would be statically referenced (similar to a ThreadLocal) but to allow dynamic injection we instead statically reference this class which can then + * dynamically fetch the correct implementation and statically retain an instance across threads within a context (such as {@link HystrixRequestContext}. + * + * @param + * + * @ExcludeFromJavadoc + */ +public class HystrixRequestVariableHolder { + + static final Logger logger = LoggerFactory.getLogger(HystrixRequestVariableHolder.class); + + private static ConcurrentHashMap> requestVariableInstance = new ConcurrentHashMap>(); + + private final HystrixRequestVariableLifecycle lifeCycleMethods; + + public HystrixRequestVariableHolder(HystrixRequestVariableLifecycle lifeCycleMethods) { + this.lifeCycleMethods = lifeCycleMethods; + } + + @SuppressWarnings("unchecked") + public T get(HystrixConcurrencyStrategy concurrencyStrategy) { + /* + * 1) Fetch RequestVariable implementation from cache. + * 2) If no implementation is found in cache then construct from factory. + * 3) Cache implementation from factory as each object instance needs to be statically cached to be relevant across threads. + */ + RVCacheKey key = new RVCacheKey(this, concurrencyStrategy); + HystrixRequestVariable rvInstance = requestVariableInstance.get(key); + if (rvInstance == null) { + requestVariableInstance.putIfAbsent(key, concurrencyStrategy.getRequestVariable(lifeCycleMethods)); + /* + * A safety check to help debug problems if someone starts injecting dynamically created HystrixConcurrencyStrategy instances - which should not be done and has no good reason to be done. + * + * The 100 value is arbitrary ... just a number far higher than we should see. + */ + if (requestVariableInstance.size() > 100) { + logger.warn("Over 100 instances of HystrixRequestVariable are being stored. This is likely the sign of a memory leak caused by using unique instances of HystrixConcurrencyStrategy instead of a single instance."); + } + } + + return (T) requestVariableInstance.get(key).get(); + } + + private static class RVCacheKey { + + private final HystrixRequestVariableHolder rvHolder; + private final HystrixConcurrencyStrategy concurrencyStrategy; + + private RVCacheKey(HystrixRequestVariableHolder rvHolder, HystrixConcurrencyStrategy concurrencyStrategy) { + this.rvHolder = rvHolder; + this.concurrencyStrategy = concurrencyStrategy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((concurrencyStrategy == null) ? 0 : concurrencyStrategy.hashCode()); + result = prime * result + ((rvHolder == null) ? 0 : rvHolder.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RVCacheKey other = (RVCacheKey) obj; + if (concurrencyStrategy == null) { + if (other.concurrencyStrategy != null) + return false; + } else if (!concurrencyStrategy.equals(other.concurrencyStrategy)) + return false; + if (rvHolder == null) { + if (other.rvHolder != null) + return false; + } else if (!rvHolder.equals(other.rvHolder)) + return false; + return true; + } + + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableLifecycle.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableLifecycle.java new file mode 100644 index 0000000..a6d2e29 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixRequestVariableLifecycle.java @@ -0,0 +1,48 @@ +/** + * Copyright 2012 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.strategy.concurrency; + +/** + * Interface for lifecycle methods that are then executed by an implementation of {@link HystrixRequestVariable}. + * + * @param + */ +public interface HystrixRequestVariableLifecycle { + + /** + * Invoked when {@link HystrixRequestVariable#get()} is first called. + *

+ * When using the default implementation this is invoked when {@link HystrixRequestVariableDefault#get()} is called. + * + * @return T with initial value or null if none. + */ + public T initialValue(); + + /** + * Invoked when request scope is shutdown to allow for cleanup. + *

+ * When using the default implementation this is invoked when {@link HystrixRequestContext#shutdown()} is called. + *

+ * The {@link HystrixRequestVariable#get()} method should not be called from within this method as it will result in {@link #initialValue()} being called again. + * + * @param value + * of request variable to allow cleanup activity. + *

+ * If nothing needs to be cleaned up then nothing needs to be done in this method. + */ + public void shutdown(T value); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/package-info.java new file mode 100644 index 0000000..eba832f --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2012 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. + */ +/** + * Strategy definition for concurrency related behavior and default implementation. + * + * @since 1.0.0 + */ +package com.netflix.hystrix.strategy.concurrency; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/HystrixEventNotifier.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/HystrixEventNotifier.java new file mode 100644 index 0000000..60b79e8 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/HystrixEventNotifier.java @@ -0,0 +1,72 @@ +/** + * Copyright 2012 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.strategy.eventnotifier; + +import java.util.List; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Abstract EventNotifier that allows receiving notifications for different events with default implementations. + *

+ * See {@link HystrixPlugins} or the Hystrix GitHub Wiki for information on configuring plugins: https://github.com/Netflix/Hystrix/wiki/Plugins. + *

+ * Note on thread-safety and performance + *

+ * A single implementation of this class will be used globally so methods on this class will be invoked concurrently from multiple threads so all functionality must be thread-safe. + *

+ * Methods are also invoked synchronously and will add to execution time of the commands so all behavior should be fast. If anything time-consuming is to be done it should be spawned asynchronously + * onto separate worker threads. + */ +public abstract class HystrixEventNotifier { + + /** + * Called for every event fired. + *

+ * Default Implementation: Does nothing + * + * @param eventType event type + * @param key event key + */ + public void markEvent(HystrixEventType eventType, HystrixCommandKey key) { + // do nothing + } + + /** + * Called after a command is executed using thread isolation. + *

+ * Will not get called if a command is rejected, short-circuited etc. + *

+ * Default Implementation: Does nothing + * + * @param key + * {@link HystrixCommandKey} of command instance. + * @param isolationStrategy + * {@link ExecutionIsolationStrategy} the isolation strategy used by the command when executed + * @param duration + * time in milliseconds of executing run() method + * @param eventsDuringExecution + * {@code List} of events occurred during execution. + */ + public void markCommandExecution(HystrixCommandKey key, ExecutionIsolationStrategy isolationStrategy, int duration, List eventsDuringExecution) { + // do nothing + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/HystrixEventNotifierDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/HystrixEventNotifierDefault.java new file mode 100644 index 0000000..77a0cac --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/HystrixEventNotifierDefault.java @@ -0,0 +1,37 @@ +/** + * Copyright 2012 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.strategy.eventnotifier; + + +/** + * Default implementations of {@link HystrixEventNotifier} that does nothing. + * + * @ExcludeFromJavadoc + */ +public class HystrixEventNotifierDefault extends HystrixEventNotifier { + + private static HystrixEventNotifierDefault INSTANCE = new HystrixEventNotifierDefault(); + + private HystrixEventNotifierDefault() { + + } + + public static HystrixEventNotifier getInstance() { + return INSTANCE; + } + + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/package-info.java new file mode 100644 index 0000000..0d77d95 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/eventnotifier/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2012 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. + */ +/** + * Strategy definition for event notification. + * + * @since 1.0.0 + */ +package com.netflix.hystrix.strategy.eventnotifier; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHook.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHook.java new file mode 100644 index 0000000..e268d88 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHook.java @@ -0,0 +1,529 @@ +/** + * Copyright 2013 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.strategy.executionhook; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Abstract ExecutionHook with invocations at different lifecycle points of {@link HystrixCommand} + * and {@link HystrixObservableCommand} execution with default no-op implementations. + *

+ * See {@link HystrixPlugins} or the Hystrix GitHub Wiki for information on configuring plugins: https://github.com/Netflix/Hystrix/wiki/Plugins. + *

+ * Note on thread-safety and performance + *

+ * A single implementation of this class will be used globally so methods on this class will be invoked concurrently from multiple threads so all functionality must be thread-safe. + *

+ * Methods are also invoked synchronously and will add to execution time of the commands so all behavior should be fast. If anything time-consuming is to be done it should be spawned asynchronously + * onto separate worker threads. + * + * @since 1.2 + * */ +public abstract class HystrixCommandExecutionHook { + + /** + * Invoked before {@link HystrixInvokable} begins executing. + * + * @param commandInstance The executing HystrixInvokable instance. + * + * @since 1.2 + */ + public void onStart(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * Invoked when {@link HystrixInvokable} emits a value. + * + * @param commandInstance The executing HystrixInvokable instance. + * @param value value emitted + * + * @since 1.4 + */ + public T onEmit(HystrixInvokable commandInstance, T value) { + return value; //by default, just pass through + } + + /** + * Invoked when {@link HystrixInvokable} fails with an Exception. + * + * @param commandInstance The executing HystrixInvokable instance. + * @param failureType {@link FailureType} enum representing which type of error + * @param e exception object + * + * @since 1.2 + */ + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { + return e; //by default, just pass through + } + + /** + * Invoked when {@link HystrixInvokable} finishes a successful execution. + * + * @param commandInstance The executing HystrixInvokable instance. + * + * @since 1.4 + */ + public void onSuccess(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * Invoked at start of thread execution when {@link HystrixCommand} is executed using {@link ExecutionIsolationStrategy#THREAD}. + * + * @param commandInstance The executing HystrixCommand instance. + * + * @since 1.2 + */ + public void onThreadStart(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * Invoked at completion of thread execution when {@link HystrixCommand} is executed using {@link ExecutionIsolationStrategy#THREAD}. + * This will get invoked whenever the Hystrix thread is done executing, regardless of whether the thread finished + * naturally, or was unsubscribed externally + * + * @param commandInstance The executing HystrixCommand instance. + * + * @since 1.2 + */ + public void onThreadComplete(HystrixInvokable commandInstance) { + // do nothing by default + } + + /** + * Invoked when the user-defined execution method in {@link HystrixInvokable} starts. + * + * @param commandInstance The executing HystrixInvokable instance. + * + * @since 1.4 + */ + public void onExecutionStart(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * Invoked when the user-defined execution method in {@link HystrixInvokable} emits a value. + * + * @param commandInstance The executing HystrixInvokable instance. + * @param value value emitted + * + * @since 1.4 + */ + public T onExecutionEmit(HystrixInvokable commandInstance, T value) { + return value; //by default, just pass through + } + + /** + * Invoked when the user-defined execution method in {@link HystrixInvokable} fails with an Exception. + * + * @param commandInstance The executing HystrixInvokable instance. + * @param e exception object + * + * @since 1.4 + */ + public Exception onExecutionError(HystrixInvokable commandInstance, Exception e) { + return e; //by default, just pass through + } + + /** + * Invoked when the user-defined execution method in {@link HystrixInvokable} completes successfully. + * + * @param commandInstance The executing HystrixInvokable instance. + * + * @since 1.4 + */ + public void onExecutionSuccess(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * Invoked when the fallback method in {@link HystrixInvokable} starts. + * + * @param commandInstance The executing HystrixInvokable instance. + * + * @since 1.2 + */ + public void onFallbackStart(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * Invoked when the fallback method in {@link HystrixInvokable} emits a value. + * + * @param commandInstance The executing HystrixInvokable instance. + * @param value value emitted + * + * @since 1.4 + */ + public T onFallbackEmit(HystrixInvokable commandInstance, T value) { + return value; //by default, just pass through + } + + /** + * Invoked when the fallback method in {@link HystrixInvokable} fails with an Exception. + * + * @param commandInstance The executing HystrixInvokable instance. + * @param e exception object + * + * @since 1.2 + */ + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { + //by default, just pass through + return e; + } + + /** + * Invoked when the user-defined execution method in {@link HystrixInvokable} completes successfully. + * + * @param commandInstance The executing HystrixInvokable instance. + * + * @since 1.4 + */ + public void onFallbackSuccess(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * Invoked when the command response is found in the {@link com.netflix.hystrix.HystrixRequestCache}. + * + * @param commandInstance The executing HystrixCommand + * + * @since 1.4 + */ + public void onCacheHit(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * Invoked with the command is unsubscribed before a terminal state + * + * @param commandInstance The executing HystrixInvokable instance. + * + * @since 1.5.9 + */ + public void onUnsubscribe(HystrixInvokable commandInstance) { + //do nothing by default + } + + /** + * DEPRECATED: Change usages of this to {@link #onExecutionStart}. + * + * Invoked before {@link HystrixCommand#run()} is about to be executed. + * + * @param commandInstance + * The executing HystrixCommand instance. + * + * @since 1.2 + */ + @Deprecated + public void onRunStart(HystrixCommand commandInstance) { + // do nothing by default + } + + /** + * DEPRECATED: Change usages of this to {@link #onExecutionStart}. + * + * Invoked before {@link HystrixCommand#run()} is about to be executed. + * + * @param commandInstance + * The executing HystrixCommand instance. + * + * @since 1.2 + */ + @Deprecated + public void onRunStart(HystrixInvokable commandInstance) { + // do nothing by default + } + + /** + * DEPRECATED: Change usages of this to {@link #onExecutionEmit} if you want to add a hook for each value emitted by the command + * or to {@link #onExecutionSuccess} if you want to add a hook when the command successfully executes + * + * Invoked after successful execution of {@link HystrixCommand#run()} with response value. + * In a {@link HystrixCommand} using {@link ExecutionIsolationStrategy#THREAD}, this will get invoked if the Hystrix thread + * successfully runs, regardless of whether the calling thread encountered a timeout. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param response + * from {@link HystrixCommand#run()} + * @return T response object that can be modified, decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public T onRunSuccess(HystrixCommand commandInstance, T response) { + // pass-thru by default + return response; + } + + /** + * DEPRECATED: Change usages of this to {@link #onExecutionEmit} if you want to add a hook for each value emitted by the command + * or to {@link #onExecutionSuccess} if you want to add a hook when the command successfully executes + * + * Invoked after successful execution of {@link HystrixCommand#run()} with response value. + * In a {@link HystrixCommand} using {@link ExecutionIsolationStrategy#THREAD}, this will get invoked if the Hystrix thread + * successfully runs, regardless of whether the calling thread encountered a timeout. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param response + * from {@link HystrixCommand#run()} + * @return T response object that can be modified, decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public T onRunSuccess(HystrixInvokable commandInstance, T response) { + // pass-thru by default + return response; + } + + /** + * DEPRECATED: Change usages of this to {@link #onExecutionError} + * + * Invoked after failed execution of {@link HystrixCommand#run()} with thrown Exception. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param e + * Exception thrown by {@link HystrixCommand#run()} + * @return Exception that can be decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public Exception onRunError(HystrixCommand commandInstance, Exception e) { + // pass-thru by default + return e; + } + + /** + * DEPRECATED: Change usages of this to {@link #onExecutionError} + * + * Invoked after failed execution of {@link HystrixCommand#run()} with thrown Exception. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param e + * Exception thrown by {@link HystrixCommand#run()} + * @return Exception that can be decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { + // pass-thru by default + return e; + } + + /** + * DEPRECATED: Change usages of this to {@link #onFallbackStart} + * + * Invoked before {@link HystrixCommand#getFallback()} is about to be executed. + * + * @param commandInstance + * The executing HystrixCommand instance. + * + * @since 1.2 + */ + @Deprecated + public void onFallbackStart(HystrixCommand commandInstance) { + // do nothing by default + } + + /** + * DEPRECATED: Change usages of this to {@link #onFallbackEmit} if you want to write a hook that handles each emitted fallback value + * or to {@link #onFallbackSuccess} if you want to write a hook that handles success of the fallback method + * + * Invoked after successful execution of {@link HystrixCommand#getFallback()} with response value. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param fallbackResponse + * from {@link HystrixCommand#getFallback()} + * @return T response object that can be modified, decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public T onFallbackSuccess(HystrixCommand commandInstance, T fallbackResponse) { + // pass-thru by default + return fallbackResponse; + } + + /** + * DEPRECATED: Change usages of this to {@link #onFallbackEmit} if you want to write a hook that handles each emitted fallback value + * or to {@link #onFallbackSuccess} if you want to write a hook that handles success of the fallback method + * + * Invoked after successful execution of {@link HystrixCommand#getFallback()} with response value. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param fallbackResponse + * from {@link HystrixCommand#getFallback()} + * @return T response object that can be modified, decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public T onFallbackSuccess(HystrixInvokable commandInstance, T fallbackResponse) { + // pass-thru by default + return fallbackResponse; + } + + /** + * DEPRECATED: Change usages of this to {@link #onFallbackError}. + * + * Invoked after failed execution of {@link HystrixCommand#getFallback()} with thrown exception. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param e + * Exception thrown by {@link HystrixCommand#getFallback()} + * @return Exception that can be decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public Exception onFallbackError(HystrixCommand commandInstance, Exception e) { + // pass-thru by default + return e; + } + + /** + * DEPRECATED: Change usages of this to {@link #onStart}. + * + * Invoked before {@link HystrixCommand} executes. + * + * @param commandInstance + * The executing HystrixCommand instance. + * + * @since 1.2 + */ + @Deprecated + public void onStart(HystrixCommand commandInstance) { + // do nothing by default + } + + /** + * DEPRECATED: Change usages of this to {@link #onEmit} if you want to write a hook that handles each emitted command value + * or to {@link #onSuccess} if you want to write a hook that handles success of the command + * + * Invoked after completion of {@link HystrixCommand} execution that results in a response. + *

+ * The response can come either from {@link HystrixCommand#run()} or {@link HystrixCommand#getFallback()}. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param response + * from {@link HystrixCommand#run()} or {@link HystrixCommand#getFallback()}. + * @return T response object that can be modified, decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public T onComplete(HystrixCommand commandInstance, T response) { + // pass-thru by default + return response; + } + + /** + * DEPRECATED: Change usages of this to {@link #onEmit} if you want to write a hook that handles each emitted command value + * or to {@link #onSuccess} if you want to write a hook that handles success of the command + * + * Invoked after completion of {@link HystrixCommand} execution that results in a response. + *

+ * The response can come either from {@link HystrixCommand#run()} or {@link HystrixCommand#getFallback()}. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param response + * from {@link HystrixCommand#run()} or {@link HystrixCommand#getFallback()}. + * @return T response object that can be modified, decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public T onComplete(HystrixInvokable commandInstance, T response) { + // pass-thru by default + return response; + } + + /** + * DEPRECATED: Change usages of this to {@link #onError}. + * + * Invoked after failed completion of {@link HystrixCommand} execution. + * + * @param commandInstance + * The executing HystrixCommand instance. + * @param failureType + * {@link FailureType} representing the type of failure that occurred. + *

+ * See {@link HystrixRuntimeException} for more information. + * @param e + * Exception thrown by {@link HystrixCommand} + * @return Exception that can be decorated, replaced or just returned as a pass-thru. + * + * @since 1.2 + */ + @Deprecated + public Exception onError(HystrixCommand commandInstance, FailureType failureType, Exception e) { + // pass-thru by default + return e; + } + + /** + * DEPRECATED: Change usages of this to {@link #onThreadStart}. + * + * Invoked at start of thread execution when {@link HystrixCommand} is executed using {@link ExecutionIsolationStrategy#THREAD}. + * + * @param commandInstance + * The executing HystrixCommand instance. + * + * @since 1.2 + */ + @Deprecated + public void onThreadStart(HystrixCommand commandInstance) { + // do nothing by default + } + + /** + * DEPRECATED: Change usages of this to {@link #onThreadComplete}. + * + * Invoked at completion of thread execution when {@link HystrixCommand} is executed using {@link ExecutionIsolationStrategy#THREAD}. + * This will get invoked if the Hystrix thread successfully executes, regardless of whether the calling thread + * encountered a timeout. + * + * @param commandInstance + * The executing HystrixCommand instance. + * + * @since 1.2 + */ + @Deprecated + public void onThreadComplete(HystrixCommand commandInstance) { + // do nothing by default + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHookDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHookDefault.java new file mode 100644 index 0000000..89820ed --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHookDefault.java @@ -0,0 +1,35 @@ +/** + * Copyright 2013 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.strategy.executionhook; + +/** + * Default implementations of {@link HystrixCommandExecutionHook} that does nothing. + * + * @ExcludeFromJavadoc + */ +public class HystrixCommandExecutionHookDefault extends HystrixCommandExecutionHook { + + private static HystrixCommandExecutionHookDefault INSTANCE = new HystrixCommandExecutionHookDefault(); + + private HystrixCommandExecutionHookDefault() { + + } + + public static HystrixCommandExecutionHook getInstance() { + return INSTANCE; + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/package-info.java new file mode 100644 index 0000000..1039967 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2013 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. + */ +/** + * Strategy definition for execution hook. + * + * @since 1.2.0 + */ +package com.netflix.hystrix.strategy.executionhook; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisher.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisher.java new file mode 100644 index 0000000..dc65065 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisher.java @@ -0,0 +1,115 @@ +/** + * Copyright 2012 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.strategy.metrics; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Abstract class with default implementations of Factory methods for creating "Metrics Publisher" instances for getting metrics and other related data + * exposed, published or otherwise retrievable by external systems such as Servo (https://github.com/Netflix/servo) + * for monitoring and statistical purposes. + *

+ * See {@link HystrixPlugins} or the Hystrix GitHub Wiki for information on configuring plugins: https://github.com/Netflix/Hystrix/wiki/Plugins. + */ +public abstract class HystrixMetricsPublisher { + + // TODO should this have cacheKey functionality like HystrixProperties does? + // I think we do otherwise dynamically provided owner and properties won't work + // a custom override would need the caching strategy for properties/publisher/owner etc to be in sync + + /** + * Construct an implementation of {@link HystrixMetricsPublisherCommand} for {@link HystrixCommand} instances having key {@link HystrixCommandKey}. + *

+ * This will be invoked once per {@link HystrixCommandKey} instance. + *

+ * Default Implementation + *

+ * Return instance of {@link HystrixMetricsPublisherCommandDefault} + * + * @param commandKey + * {@link HystrixCommandKey} representing the name or type of {@link HystrixCommand} + * @param commandGroupKey + * {@link HystrixCommandGroupKey} of {@link HystrixCommand} + * @param metrics + * {@link HystrixCommandMetrics} instance tracking metrics for {@link HystrixCommand} instances having the key as defined by {@link HystrixCommandKey} + * @param circuitBreaker + * {@link HystrixCircuitBreaker} instance for {@link HystrixCommand} instances having the key as defined by {@link HystrixCommandKey} + * @param properties + * {@link HystrixCommandProperties} instance for {@link HystrixCommand} instances having the key as defined by {@link HystrixCommandKey} + * @return instance of {@link HystrixMetricsPublisherCommand} that will have its initialize method invoked once. + */ + public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + return new HystrixMetricsPublisherCommandDefault(commandKey, commandGroupKey, metrics, circuitBreaker, properties); + } + + /** + * Construct an implementation of {@link HystrixMetricsPublisherThreadPool} for {@link HystrixThreadPool} instances having key {@link HystrixThreadPoolKey}. + *

+ * This will be invoked once per {@link HystrixThreadPoolKey} instance. + *

+ * Default Implementation + *

+ * Return instance of {@link HystrixMetricsPublisherThreadPoolDefault} + * + * @param threadPoolKey + * {@link HystrixThreadPoolKey} representing the name or type of {@link HystrixThreadPool} + * @param metrics + * {@link HystrixThreadPoolMetrics} instance tracking metrics for the {@link HystrixThreadPool} instance having the key as defined by {@link HystrixThreadPoolKey} + * @param properties + * {@link HystrixThreadPoolProperties} instance for the {@link HystrixThreadPool} instance having the key as defined by {@link HystrixThreadPoolKey} + * @return instance of {@link HystrixMetricsPublisherThreadPool} that will have its initialize method invoked once. + */ + public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + return new HystrixMetricsPublisherThreadPoolDefault(threadPoolKey, metrics, properties); + } + + /** + * Construct an implementation of {@link HystrixMetricsPublisherCollapser} for {@link HystrixCollapser} instances having key {@link HystrixCollapserKey}. + *

+ * This will be invoked once per {@link HystrixCollapserKey} instance. + *

+ * Default Implementation + *

+ * Return instance of {@link HystrixMetricsPublisherCollapserDefault} + * + * @param collapserKey + * {@link HystrixCollapserKey} representing the name or type of {@link HystrixCollapser} + * @param metrics + * {@link HystrixCollapserMetrics} instance tracking metrics for the {@link HystrixCollapser} instance having the key as defined by {@link HystrixCollapserKey} + * @param properties + * {@link HystrixCollapserProperties} instance for the {@link HystrixCollapser} instance having the key as defined by {@link HystrixCollapserKey} + * @return instance of {@link HystrixMetricsPublisherCollapser} that will have its initialize method invoked once. + */ + public HystrixMetricsPublisherCollapser getMetricsPublisherForCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { + return new HystrixMetricsPublisherCollapserDefault(collapserKey, metrics, properties); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCollapser.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCollapser.java new file mode 100644 index 0000000..239d11e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCollapser.java @@ -0,0 +1,36 @@ +/** + * Copyright 2012 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.strategy.metrics; + +import com.netflix.hystrix.HystrixCollapser; + +/** + * Metrics publisher for a {@link HystrixCollapser} that will be constructed by an implementation of {@link HystrixMetricsPublisher}. + *

+ * Instantiation of implementations of this interface should NOT allocate resources that require shutdown, register listeners or other such global state changes. + *

+ * The initialize() method will be called once-and-only-once to indicate when this instance can register with external services, start publishing metrics etc. + *

+ * Doing this logic in the constructor could result in memory leaks, double-publishing and other such behavior because this can be optimistically constructed more than once and "extras" discarded with + * only one actually having initialize() called on it. + */ +public interface HystrixMetricsPublisherCollapser { + + // TODO should the arguments be given via initialize rather than constructor so people can't accidentally do it wrong? + + public void initialize(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCollapserDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCollapserDefault.java new file mode 100644 index 0000000..da78682 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCollapserDefault.java @@ -0,0 +1,40 @@ +/** + * Copyright 2012 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.strategy.metrics; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; + +/** + * Default implementation of {@link HystrixMetricsPublisherCollapser} that does nothing. + *

+ * See Wiki docs about plugins for more information. + * + * @ExcludeFromJavadoc + */ +public class HystrixMetricsPublisherCollapserDefault implements HystrixMetricsPublisherCollapser { + + public HystrixMetricsPublisherCollapserDefault(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { + // do nothing by default + } + + @Override + public void initialize() { + // do nothing by default + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCommand.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCommand.java new file mode 100644 index 0000000..0e3ff89 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCommand.java @@ -0,0 +1,36 @@ +/** + * Copyright 2012 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.strategy.metrics; + +import com.netflix.hystrix.HystrixCommand; + +/** + * Metrics publisher for a {@link HystrixCommand} that will be constructed by an implementation of {@link HystrixMetricsPublisher}. + *

+ * Instantiation of implementations of this interface should NOT allocate resources that require shutdown, register listeners or other such global state changes. + *

+ * The initialize() method will be called once-and-only-once to indicate when this instance can register with external services, start publishing metrics etc. + *

+ * Doing this logic in the constructor could result in memory leaks, double-publishing and other such behavior because this can be optimistically constructed more than once and "extras" discarded with + * only one actually having initialize() called on it. + */ +public interface HystrixMetricsPublisherCommand { + + // TODO should the arguments be given via initialize rather than constructor so they can't accidentally do it wrong? + + public void initialize(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCommandDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCommandDefault.java new file mode 100644 index 0000000..e9305ec --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherCommandDefault.java @@ -0,0 +1,42 @@ +/** + * Copyright 2012 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.strategy.metrics; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; + +/** + * Default implementation of {@link HystrixMetricsPublisherCommand} that does nothing. + *

+ * See Wiki docs about plugins for more information. + * + * @ExcludeFromJavadoc + */ +public class HystrixMetricsPublisherCommandDefault implements HystrixMetricsPublisherCommand { + + public HystrixMetricsPublisherCommandDefault(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + // do nothing by default + } + + @Override + public void initialize() { + // do nothing by default + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherDefault.java new file mode 100644 index 0000000..7fb3175 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherDefault.java @@ -0,0 +1,36 @@ +/** + * Copyright 2012 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.strategy.metrics; + +/** + * Default implementation of {@link HystrixMetricsPublisher}. + *

+ * See Wiki docs about plugins for more information. + * + * @ExcludeFromJavadoc + */ +public class HystrixMetricsPublisherDefault extends HystrixMetricsPublisher { + + private static HystrixMetricsPublisherDefault INSTANCE = new HystrixMetricsPublisherDefault(); + + public static HystrixMetricsPublisher getInstance() { + return INSTANCE; + } + + private HystrixMetricsPublisherDefault() { + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactory.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactory.java new file mode 100644 index 0000000..98c7813 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactory.java @@ -0,0 +1,188 @@ +/** + * Copyright 2012 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.strategy.metrics; + +import java.util.concurrent.ConcurrentHashMap; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Factory for retrieving metrics publisher implementations. + *

+ * This uses given {@link HystrixMetricsPublisher} implementations to construct publisher instances and caches each instance according to the cache key provided. + * + * @ExcludeFromJavadoc + */ +public class HystrixMetricsPublisherFactory { + + /** + * The SINGLETON instance for real use. + *

+ * Unit tests will create instance methods for testing and injecting different publishers. + */ + private static HystrixMetricsPublisherFactory SINGLETON = new HystrixMetricsPublisherFactory(); + + /** + * Get an instance of {@link HystrixMetricsPublisherThreadPool} with the given factory {@link HystrixMetricsPublisher} implementation for each {@link HystrixThreadPool} instance. + * + * @param threadPoolKey + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForThreadPool} implementation + * @param metrics + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForThreadPool} implementation + * @param properties + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForThreadPool} implementation + * @return {@link HystrixMetricsPublisherThreadPool} instance + */ + public static HystrixMetricsPublisherThreadPool createOrRetrievePublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + return SINGLETON.getPublisherForThreadPool(threadPoolKey, metrics, properties); + } + + /** + * Get an instance of {@link HystrixMetricsPublisherCommand} with the given factory {@link HystrixMetricsPublisher} implementation for each {@link HystrixCommand} instance. + * + * @param commandKey + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForCommand} implementation + * @param commandOwner + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForCommand} implementation + * @param metrics + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForCommand} implementation + * @param circuitBreaker + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForCommand} implementation + * @param properties + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForCommand} implementation + * @return {@link HystrixMetricsPublisherCommand} instance + */ + public static HystrixMetricsPublisherCommand createOrRetrievePublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandOwner, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + return SINGLETON.getPublisherForCommand(commandKey, commandOwner, metrics, circuitBreaker, properties); + } + + /** + * Resets the SINGLETON object. + * Clears all state from publishers. If new requests come in instances will be recreated. + * + */ + public static void reset() { + SINGLETON = new HystrixMetricsPublisherFactory(); + SINGLETON.commandPublishers.clear(); + SINGLETON.threadPoolPublishers.clear(); + SINGLETON.collapserPublishers.clear(); + } + + /* package */ HystrixMetricsPublisherFactory() {} + + // String is CommandKey.name() (we can't use CommandKey directly as we can't guarantee it implements hashcode/equals correctly) + private final ConcurrentHashMap commandPublishers = new ConcurrentHashMap(); + + /* package */ HystrixMetricsPublisherCommand getPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandOwner, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + // attempt to retrieve from cache first + HystrixMetricsPublisherCommand publisher = commandPublishers.get(commandKey.name()); + if (publisher != null) { + return publisher; + } else { + synchronized (this) { + HystrixMetricsPublisherCommand existingPublisher = commandPublishers.get(commandKey.name()); + if (existingPublisher != null) { + return existingPublisher; + } else { + HystrixMetricsPublisherCommand newPublisher = HystrixPlugins.getInstance().getMetricsPublisher().getMetricsPublisherForCommand(commandKey, commandOwner, metrics, circuitBreaker, properties); + commandPublishers.putIfAbsent(commandKey.name(), newPublisher); + newPublisher.initialize(); + return newPublisher; + } + } + } + } + + // String is ThreadPoolKey.name() (we can't use ThreadPoolKey directly as we can't guarantee it implements hashcode/equals correctly) + private final ConcurrentHashMap threadPoolPublishers = new ConcurrentHashMap(); + + /* package */ HystrixMetricsPublisherThreadPool getPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + // attempt to retrieve from cache first + HystrixMetricsPublisherThreadPool publisher = threadPoolPublishers.get(threadPoolKey.name()); + if (publisher != null) { + return publisher; + } + // it doesn't exist so we need to create it + publisher = HystrixPlugins.getInstance().getMetricsPublisher().getMetricsPublisherForThreadPool(threadPoolKey, metrics, properties); + // attempt to store it (race other threads) + HystrixMetricsPublisherThreadPool existing = threadPoolPublishers.putIfAbsent(threadPoolKey.name(), publisher); + if (existing == null) { + // we won the thread-race to store the instance we created so initialize it + publisher.initialize(); + // done registering, return instance that got cached + return publisher; + } else { + // we lost so return 'existing' and let the one we created be garbage collected + // without calling initialize() on it + return existing; + } + } + + /** + * Get an instance of {@link HystrixMetricsPublisherCollapser} with the given factory {@link HystrixMetricsPublisher} implementation for each {@link HystrixCollapser} instance. + * + * @param collapserKey + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForCollapser} implementation + * @param metrics + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForCollapser} implementation + * @param properties + * Pass-thru to {@link HystrixMetricsPublisher#getMetricsPublisherForCollapser} implementation + * @return {@link HystrixMetricsPublisherCollapser} instance + */ + public static HystrixMetricsPublisherCollapser createOrRetrievePublisherForCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { + return SINGLETON.getPublisherForCollapser(collapserKey, metrics, properties); + } + + // String is CollapserKey.name() (we can't use CollapserKey directly as we can't guarantee it implements hashcode/equals correctly) + private final ConcurrentHashMap collapserPublishers = new ConcurrentHashMap(); + + /* package */ HystrixMetricsPublisherCollapser getPublisherForCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { + // attempt to retrieve from cache first + HystrixMetricsPublisherCollapser publisher = collapserPublishers.get(collapserKey.name()); + if (publisher != null) { + return publisher; + } + // it doesn't exist so we need to create it + publisher = HystrixPlugins.getInstance().getMetricsPublisher().getMetricsPublisherForCollapser(collapserKey, metrics, properties); + // attempt to store it (race other threads) + HystrixMetricsPublisherCollapser existing = collapserPublishers.putIfAbsent(collapserKey.name(), publisher); + if (existing == null) { + // we won the thread-race to store the instance we created so initialize it + publisher.initialize(); + // done registering, return instance that got cached + return publisher; + } else { + // we lost so return 'existing' and let the one we created be garbage collected + // without calling initialize() on it + return existing; + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherThreadPool.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherThreadPool.java new file mode 100644 index 0000000..ff3507a --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherThreadPool.java @@ -0,0 +1,36 @@ +/** + * Copyright 2012 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.strategy.metrics; + +import com.netflix.hystrix.HystrixThreadPool; + +/** + * Metrics publisher for a {@link HystrixThreadPool} that will be constructed by an implementation of {@link HystrixMetricsPublisher}. + *

+ * Instantiation of implementations of this interface should NOT allocate resources that require shutdown, register listeners or other such global state changes. + *

+ * The initialize() method will be called once-and-only-once to indicate when this instance can register with external services, start publishing metrics etc. + *

+ * Doing this logic in the constructor could result in memory leaks, double-publishing and other such behavior because this can be optimistically constructed more than once and "extras" discarded with + * only one actually having initialize() called on it. + */ +public interface HystrixMetricsPublisherThreadPool { + + // TODO should the arguments be given via initialize rather than constructor so people can't accidentally do it wrong? + + public void initialize(); + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherThreadPoolDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherThreadPoolDefault.java new file mode 100644 index 0000000..d2d3284 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherThreadPoolDefault.java @@ -0,0 +1,40 @@ +/** + * Copyright 2012 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.strategy.metrics; + +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; + +/** + * Default implementation of {@link HystrixMetricsPublisherThreadPool} that does nothing. + *

+ * See Wiki docs about plugins for more information. + * + * @ExcludeFromJavadoc + */ +public class HystrixMetricsPublisherThreadPoolDefault implements HystrixMetricsPublisherThreadPool { + + public HystrixMetricsPublisherThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + // do nothing by default + } + + @Override + public void initialize() { + // do nothing by default + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/package-info.java new file mode 100644 index 0000000..d726b25 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/metrics/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2012 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. + */ +/** + * Strategy definition for publishing metrics and default implementation. + * + * @since 1.0.0 + */ +package com.netflix.hystrix.strategy.metrics; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/package-info.java new file mode 100644 index 0000000..100075f --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2012 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. + */ +/** + * Parent package of strategies and plugin management. + * + * @since 1.0.0 + */ +package com.netflix.hystrix.strategy; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicProperties.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicProperties.java new file mode 100644 index 0000000..933b13e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicProperties.java @@ -0,0 +1,98 @@ +/** + * Copyright 2016 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.strategy.properties; + +import java.util.ServiceLoader; + +/** + * A hystrix plugin (SPI) for resolving dynamic configuration properties. This + * SPI allows for varying configuration sources. + * + * The HystrixPlugin singleton will load only one implementation of this SPI + * throught the {@link ServiceLoader} mechanism. + * + * @author agentgt + * + */ +public interface HystrixDynamicProperties { + + /** + * Requests a property that may or may not actually exist. + * @param name property name, never null + * @param fallback default value, maybe null + * @return never null + */ + public HystrixDynamicProperty getString(String name, String fallback); + /** + * Requests a property that may or may not actually exist. + * @param name property name, never null + * @param fallback default value, maybe null + * @return never null + */ + public HystrixDynamicProperty getInteger(String name, Integer fallback); + /** + * Requests a property that may or may not actually exist. + * @param name property name, never null + * @param fallback default value, maybe null + * @return never null + */ + public HystrixDynamicProperty getLong(String name, Long fallback); + /** + * Requests a property that may or may not actually exist. + * @param name property name + * @param fallback default value + * @return never null + */ + public HystrixDynamicProperty getBoolean(String name, Boolean fallback); + + /** + * @ExcludeFromJavadoc + */ + public static class Util { + /** + * A convenience method to get a property by type (Class). + * @param properties never null + * @param name never null + * @param fallback maybe null + * @param type never null + * @return a dynamic property with type T. + */ + @SuppressWarnings("unchecked") + public static HystrixDynamicProperty getProperty( + HystrixDynamicProperties properties, String name, T fallback, Class type) { + return (HystrixDynamicProperty) doProperty(properties, name, fallback, type); + } + + private static HystrixDynamicProperty doProperty( + HystrixDynamicProperties delegate, + String name, Object fallback, Class type) { + if(type == String.class) { + return delegate.getString(name, (String) fallback); + } + else if (type == Integer.class) { + return delegate.getInteger(name, (Integer) fallback); + } + else if (type == Long.class) { + return delegate.getLong(name, (Long) fallback); + } + else if (type == Boolean.class) { + return delegate.getBoolean(name, (Boolean) fallback); + } + throw new IllegalStateException(); + } + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicPropertiesSystemProperties.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicPropertiesSystemProperties.java new file mode 100644 index 0000000..444c17e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicPropertiesSystemProperties.java @@ -0,0 +1,104 @@ +package com.netflix.hystrix.strategy.properties; + +/** + * @ExcludeFromJavadoc + * @author agent + */ +public final class HystrixDynamicPropertiesSystemProperties implements HystrixDynamicProperties { + + /** + * Only public for unit test purposes. + */ + public HystrixDynamicPropertiesSystemProperties() {} + + private static class LazyHolder { + private static final HystrixDynamicPropertiesSystemProperties INSTANCE = new HystrixDynamicPropertiesSystemProperties(); + } + + public static HystrixDynamicProperties getInstance() { + return LazyHolder.INSTANCE; + } + + //TODO probably should not be anonymous classes for GC reasons and possible jit method eliding. + @Override + public HystrixDynamicProperty getInteger(final String name, final Integer fallback) { + return new HystrixDynamicProperty() { + + @Override + public String getName() { + return name; + } + + @Override + public Integer get() { + return Integer.getInteger(name, fallback); + } + @Override + public void addCallback(Runnable callback) { + } + }; + } + + @Override + public HystrixDynamicProperty getString(final String name, final String fallback) { + return new HystrixDynamicProperty() { + + @Override + public String getName() { + return name; + } + + @Override + public String get() { + return System.getProperty(name, fallback); + } + + @Override + public void addCallback(Runnable callback) { + } + }; + } + + @Override + public HystrixDynamicProperty getLong(final String name, final Long fallback) { + return new HystrixDynamicProperty() { + + @Override + public String getName() { + return name; + } + + @Override + public Long get() { + return Long.getLong(name, fallback); + } + + @Override + public void addCallback(Runnable callback) { + } + }; + } + + @Override + public HystrixDynamicProperty getBoolean(final String name, final Boolean fallback) { + return new HystrixDynamicProperty() { + + @Override + public String getName() { + return name; + } + @Override + public Boolean get() { + if (System.getProperty(name) == null) { + return fallback; + } + return Boolean.getBoolean(name); + } + + @Override + public void addCallback(Runnable callback) { + } + }; + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicProperty.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicProperty.java new file mode 100644 index 0000000..fd85a54 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixDynamicProperty.java @@ -0,0 +1,40 @@ +/** + * Copyright 2016 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.strategy.properties; + +/** + * Generic interface to represent a dynamic property value so Hystrix can consume + * properties without being tied to any particular backing implementation. + * + * @author agentgt + * + * @param + * Type of property value. + * @see HystrixProperty + * @see HystrixDynamicProperties + */ +public interface HystrixDynamicProperty extends HystrixProperty{ + + public String getName(); + + /** + * Register a callback to be run if the property is updated. + * Backing implementations may choose to do nothing. + * @param callback callback. + */ + public void addCallback(Runnable callback); + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusProperty.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusProperty.java new file mode 100644 index 0000000..3499055 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusProperty.java @@ -0,0 +1,373 @@ +/** + * Copyright 2012 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.strategy.properties; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.config.PropertyWrapper; + +/** + * Chained property allowing a chain of defaults using Archaius (https://github.com/Netflix/archaius) properties which is used by the default properties implementations. + *

+ * Instead of just a single dynamic property with a default this allows a sequence of properties that fallback to the farthest down the chain with a value. + * + * TODO This should be replaced by a version in the Archaius library once available. + * + * @ExcludeFromJavadoc + */ +public abstract class HystrixPropertiesChainedArchaiusProperty { + private static final Logger logger = LoggerFactory.getLogger(HystrixPropertiesChainedArchaiusProperty.class); + + /** + * @ExcludeFromJavadoc + */ + public static abstract class ChainLink { + + private final AtomicReference> pReference; + private final ChainLink next; + private final List callbacks; + + /** + * @return String + */ + public abstract String getName(); + + /** + * @return T + */ + protected abstract T getValue(); + + /** + * @return Boolean + */ + public abstract boolean isValueAcceptable(); + + /** + * No arg constructor - used for end node + */ + public ChainLink() { + next = null; + pReference = new AtomicReference>(this); + callbacks = new ArrayList(); + } + + /** + * @param nextProperty next property in the chain + */ + public ChainLink(ChainLink nextProperty) { + next = nextProperty; + pReference = new AtomicReference>(next); + callbacks = new ArrayList(); + } + + protected void checkAndFlip() { + // in case this is the end node + if (next == null) { + pReference.set(this); + return; + } + + if (this.isValueAcceptable()) { + logger.debug("Flipping property: {} to use its current value: {}", getName(), getValue()); + pReference.set(this); + } else { + logger.debug("Flipping property: {} to use NEXT property: {}", getName(), next); + pReference.set(next); + } + + for (Runnable r : callbacks) { + r.run(); + } + } + + /** + * @return T + */ + public T get() { + if (pReference.get() == this) { + return this.getValue(); + } else { + return pReference.get().get(); + } + } + + /** + * @param r callback to execut + */ + public void addCallback(Runnable r) { + callbacks.add(r); + } + + /** + * @return String + */ + public String toString() { + return getName() + " = " + get(); + } + } + + /** + * @ExcludeFromJavadoc + */ + public static class StringProperty extends ChainLink { + + private final DynamicStringProperty sProp; + + public StringProperty(DynamicStringProperty sProperty) { + super(); + sProp = sProperty; + } + + public StringProperty(String name, DynamicStringProperty sProperty) { + this(name, new StringProperty(sProperty)); + } + + public StringProperty(String name, StringProperty next) { + this(new DynamicStringProperty(name, null), next); + } + + public StringProperty(DynamicStringProperty sProperty, DynamicStringProperty next) { + this(sProperty, new StringProperty(next)); + } + + public StringProperty(DynamicStringProperty sProperty, StringProperty next) { + super(next); // setup next pointer + + sProp = sProperty; + sProp.addCallback(new Runnable() { + @Override + public void run() { + logger.debug("Property changed: '{} = {}'", getName(), getValue()); + checkAndFlip(); + } + }); + checkAndFlip(); + } + + @Override + public boolean isValueAcceptable() { + return (sProp.get() != null); + } + + @Override + protected String getValue() { + return sProp.get(); + } + + @Override + public String getName() { + return sProp.getName(); + } + } + + /** + * @ExcludeFromJavadoc + */ + public static class IntegerProperty extends ChainLink { + + private final DynamicIntegerProperty sProp; + + public IntegerProperty(DynamicIntegerProperty sProperty) { + super(); + sProp = sProperty; + } + + public IntegerProperty(String name, DynamicIntegerProperty sProperty) { + this(name, new IntegerProperty(sProperty)); + } + + public IntegerProperty(String name, IntegerProperty next) { + this(new DynamicIntegerProperty(name, null), next); + } + + public IntegerProperty(DynamicIntegerProperty sProperty, DynamicIntegerProperty next) { + this(sProperty, new IntegerProperty(next)); + } + + public IntegerProperty(DynamicIntegerProperty sProperty, IntegerProperty next) { + super(next); // setup next pointer + + sProp = sProperty; + sProp.addCallback(new Runnable() { + @Override + public void run() { + logger.debug("Property changed: '{} = {}'", getName(), getValue()); + checkAndFlip(); + } + }); + checkAndFlip(); + } + + @Override + public boolean isValueAcceptable() { + return (sProp.get() != null); + } + + @Override + public Integer getValue() { + return sProp.get(); + } + + @Override + public String getName() { + return sProp.getName(); + } + } + + /** + * @ExcludeFromJavadoc + */ + public static class BooleanProperty extends ChainLink { + + private final DynamicBooleanProperty sProp; + + public BooleanProperty(DynamicBooleanProperty sProperty) { + super(); + sProp = sProperty; + } + + public BooleanProperty(String name, DynamicBooleanProperty sProperty) { + this(name, new BooleanProperty(sProperty)); + } + + public BooleanProperty(String name, BooleanProperty next) { + this(new DynamicBooleanProperty(name, null), next); + } + + public BooleanProperty(DynamicBooleanProperty sProperty, DynamicBooleanProperty next) { + this(sProperty, new BooleanProperty(next)); + } + + public BooleanProperty(DynamicBooleanProperty sProperty, BooleanProperty next) { + super(next); // setup next pointer + + sProp = sProperty; + sProp.addCallback(new Runnable() { + @Override + public void run() { + logger.debug("Property changed: '{} = {}'", getName(), getValue()); + checkAndFlip(); + } + }); + checkAndFlip(); + } + + @Override + public boolean isValueAcceptable() { + return (sProp.getValue() != null); + } + + @Override + public Boolean getValue() { + return sProp.get(); + } + + @Override + public String getName() { + return sProp.getName(); + } + } + + /** + * @ExcludeFromJavadoc + */ + public static class DynamicBooleanProperty extends PropertyWrapper { + public DynamicBooleanProperty(String propName, Boolean defaultValue) { + super(propName, defaultValue); + } + + /** + * Get the current value from the underlying DynamicProperty + */ + public Boolean get() { + return prop.getBoolean(defaultValue); + } + + @Override + public Boolean getValue() { + return get(); + } + } + + /** + * @ExcludeFromJavadoc + */ + public static class DynamicIntegerProperty extends PropertyWrapper { + public DynamicIntegerProperty(String propName, Integer defaultValue) { + super(propName, defaultValue); + } + + /** + * Get the current value from the underlying DynamicProperty + */ + public Integer get() { + return prop.getInteger(defaultValue); + } + + @Override + public Integer getValue() { + return get(); + } + } + + /** + * @ExcludeFromJavadoc + */ + public static class DynamicLongProperty extends PropertyWrapper { + public DynamicLongProperty(String propName, Long defaultValue) { + super(propName, defaultValue); + } + + /** + * Get the current value from the underlying DynamicProperty + */ + public Long get() { + return prop.getLong(defaultValue); + } + + @Override + public Long getValue() { + return get(); + } + } + + /** + * @ExcludeFromJavadoc + */ + public static class DynamicStringProperty extends PropertyWrapper { + public DynamicStringProperty(String propName, String defaultValue) { + super(propName, defaultValue); + } + + /** + * Get the current value from the underlying DynamicProperty + */ + public String get() { + return prop.getString(defaultValue); + } + + @Override + public String getValue() { + return get(); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedProperty.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedProperty.java new file mode 100644 index 0000000..ea56d1e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedProperty.java @@ -0,0 +1,268 @@ +/** + * Copyright 2016 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.strategy.properties; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Chained property allowing a chain of defaults properties which is uses the properties plugin. + *

+ * Instead of just a single dynamic property with a default this allows a sequence of properties that fallback to the farthest down the chain with a value. + * + * TODO This should be replaced by a version in the Archaius library once available. + * + * @ExcludeFromJavadoc + */ +public abstract class HystrixPropertiesChainedProperty { + private static final Logger logger = LoggerFactory.getLogger(HystrixPropertiesChainedProperty.class); + + /** + * @ExcludeFromJavadoc + */ + private static abstract class ChainLink { + + private final AtomicReference> pReference; + private final ChainLink next; + private final List callbacks; + + /** + * @return String + */ + public abstract String getName(); + + /** + * @return T + */ + protected abstract T getValue(); + + /** + * @return Boolean + */ + public abstract boolean isValueAcceptable(); + + /** + * No arg constructor - used for end node + */ + public ChainLink() { + next = null; + pReference = new AtomicReference>(this); + callbacks = new ArrayList(); + } + + /** + * @param nextProperty next property in the chain + */ + public ChainLink(ChainLink nextProperty) { + next = nextProperty; + pReference = new AtomicReference>(next); + callbacks = new ArrayList(); + } + + protected void checkAndFlip() { + // in case this is the end node + if (next == null) { + pReference.set(this); + return; + } + + if (this.isValueAcceptable()) { + logger.debug("Flipping property: {} to use its current value: {}", getName(), getValue()); + pReference.set(this); + } else { + logger.debug("Flipping property: {} to use NEXT property: {}", getName(), next); + pReference.set(next); + } + + for (Runnable r : callbacks) { + r.run(); + } + } + + /** + * @return T + */ + public T get() { + if (pReference.get() == this) { + return this.getValue(); + } else { + return pReference.get().get(); + } + } + + /** + * @param r callback to execut + */ + public void addCallback(Runnable r) { + callbacks.add(r); + } + + /** + * @return String + */ + public String toString() { + return getName() + " = " + get(); + } + } + + public static abstract class ChainBuilder { + + private ChainBuilder() { + super(); + } + + private List> properties = + new ArrayList>(); + + + public ChainBuilder add(HystrixDynamicProperty property) { + properties.add(property); + return this; + } + + public ChainBuilder add(String name, T defaultValue) { + properties.add(getDynamicProperty(name, defaultValue, getType())); + return this; + } + + public HystrixDynamicProperty build() { + if (properties.size() < 1) throw new IllegalArgumentException(); + if (properties.size() == 1) return properties.get(0); + List> reversed = + new ArrayList>(properties); + Collections.reverse(reversed); + ChainProperty current = null; + for (HystrixDynamicProperty p : reversed) { + if (current == null) { + current = new ChainProperty(p); + } + else { + current = new ChainProperty(p, current); + } + } + + return new ChainHystrixProperty(current); + + } + + protected abstract Class getType(); + + } + + private static ChainBuilder forType(final Class type) { + return new ChainBuilder() { + @Override + protected Class getType() { + return type; + } + }; + } + + public static ChainBuilder forString() { + return forType(String.class); + } + public static ChainBuilder forInteger() { + return forType(Integer.class); + } + public static ChainBuilder forBoolean() { + return forType(Boolean.class); + } + public static ChainBuilder forLong() { + return forType(Long.class); + } + + private static class ChainHystrixProperty implements HystrixDynamicProperty { + private final ChainProperty property; + + public ChainHystrixProperty(ChainProperty property) { + super(); + this.property = property; + } + + @Override + public String getName() { + return property.getName(); + } + + @Override + public T get() { + return property.get(); + } + + @Override + public void addCallback(Runnable callback) { + property.addCallback(callback); + } + + } + + private static class ChainProperty extends ChainLink { + + private final HystrixDynamicProperty sProp; + + public ChainProperty(HystrixDynamicProperty sProperty) { + super(); + sProp = sProperty; + } + + + public ChainProperty(HystrixDynamicProperty sProperty, ChainProperty next) { + super(next); // setup next pointer + + sProp = sProperty; + sProp.addCallback(new Runnable() { + @Override + public void run() { + logger.debug("Property changed: '{} = {}'", getName(), getValue()); + checkAndFlip(); + } + }); + checkAndFlip(); + } + + @Override + public boolean isValueAcceptable() { + return (sProp.get() != null); + } + + @Override + protected T getValue() { + return sProp.get(); + } + + @Override + public String getName() { + return sProp.getName(); + } + + } + + private static HystrixDynamicProperty + getDynamicProperty(String propName, T defaultValue, Class type) { + HystrixDynamicProperties properties = HystrixPlugins.getInstance().getDynamicProperties(); + HystrixDynamicProperty p = + HystrixDynamicProperties.Util.getProperty(properties, propName, defaultValue, type); + return p; + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesCollapserDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesCollapserDefault.java new file mode 100644 index 0000000..7d4af6b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesCollapserDefault.java @@ -0,0 +1,32 @@ +/** + * Copyright 2012 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.strategy.properties; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; + +/** + * Default implementation of {@link HystrixCollapserProperties} using Archaius (https://github.com/Netflix/archaius) + * + * @ExcludeFromJavadoc + */ +public class HystrixPropertiesCollapserDefault extends HystrixCollapserProperties { + + public HystrixPropertiesCollapserDefault(HystrixCollapserKey collapserKey, Setter builder) { + super(collapserKey, builder); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesCommandDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesCommandDefault.java new file mode 100644 index 0000000..c297c70 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesCommandDefault.java @@ -0,0 +1,32 @@ +/** + * Copyright 2012 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.strategy.properties; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; + +/** + * Default implementation of {@link HystrixCommandProperties} using Archaius (https://github.com/Netflix/archaius) + * + * @ExcludeFromJavadoc + */ +public class HystrixPropertiesCommandDefault extends HystrixCommandProperties { + + public HystrixPropertiesCommandDefault(HystrixCommandKey key, Setter builder) { + super(key, builder); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesFactory.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesFactory.java new file mode 100644 index 0000000..792d7ab --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesFactory.java @@ -0,0 +1,166 @@ +/** + * Copyright 2012 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.strategy.properties; + +import java.util.concurrent.ConcurrentHashMap; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Factory for retrieving properties implementations. + *

+ * This uses given {@link HystrixPropertiesStrategy} implementations to construct Properties instances and caches each instance according to the cache key provided. + * + * @ExcludeFromJavadoc + */ +public class HystrixPropertiesFactory { + + /** + * Clears all the defaults in the static property cache. This makes it possible for property defaults to not persist for + * an entire JVM lifetime. May be invoked directly, and also gets invoked by Hystrix.reset() + */ + public static void reset() { + commandProperties.clear(); + threadPoolProperties.clear(); + collapserProperties.clear(); + } + + // String is CommandKey.name() (we can't use CommandKey directly as we can't guarantee it implements hashcode/equals correctly) + private static final ConcurrentHashMap commandProperties = new ConcurrentHashMap(); + + /** + * Get an instance of {@link HystrixCommandProperties} with the given factory {@link HystrixPropertiesStrategy} implementation for each {@link HystrixCommand} instance. + * + * @param key + * Pass-thru to {@link HystrixPropertiesStrategy#getCommandProperties} implementation. + * @param builder + * Pass-thru to {@link HystrixPropertiesStrategy#getCommandProperties} implementation. + * @return {@link HystrixCommandProperties} instance + */ + public static HystrixCommandProperties getCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder) { + HystrixPropertiesStrategy hystrixPropertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); + String cacheKey = hystrixPropertiesStrategy.getCommandPropertiesCacheKey(key, builder); + if (cacheKey != null) { + HystrixCommandProperties properties = commandProperties.get(cacheKey); + if (properties != null) { + return properties; + } else { + if (builder == null) { + builder = HystrixCommandProperties.Setter(); + } + // create new instance + properties = hystrixPropertiesStrategy.getCommandProperties(key, builder); + // cache and return + HystrixCommandProperties existing = commandProperties.putIfAbsent(cacheKey, properties); + if (existing == null) { + return properties; + } else { + return existing; + } + } + } else { + // no cacheKey so we generate it with caching + return hystrixPropertiesStrategy.getCommandProperties(key, builder); + } + } + + // String is ThreadPoolKey.name() (we can't use ThreadPoolKey directly as we can't guarantee it implements hashcode/equals correctly) + private static final ConcurrentHashMap threadPoolProperties = new ConcurrentHashMap(); + + /** + * Get an instance of {@link HystrixThreadPoolProperties} with the given factory {@link HystrixPropertiesStrategy} implementation for each {@link HystrixThreadPool} instance. + * + * @param key + * Pass-thru to {@link HystrixPropertiesStrategy#getThreadPoolProperties} implementation. + * @param builder + * Pass-thru to {@link HystrixPropertiesStrategy#getThreadPoolProperties} implementation. + * @return {@link HystrixThreadPoolProperties} instance + */ + public static HystrixThreadPoolProperties getThreadPoolProperties(HystrixThreadPoolKey key, HystrixThreadPoolProperties.Setter builder) { + HystrixPropertiesStrategy hystrixPropertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); + String cacheKey = hystrixPropertiesStrategy.getThreadPoolPropertiesCacheKey(key, builder); + if (cacheKey != null) { + HystrixThreadPoolProperties properties = threadPoolProperties.get(cacheKey); + if (properties != null) { + return properties; + } else { + if (builder == null) { + builder = HystrixThreadPoolProperties.Setter(); + } + // create new instance + properties = hystrixPropertiesStrategy.getThreadPoolProperties(key, builder); + // cache and return + HystrixThreadPoolProperties existing = threadPoolProperties.putIfAbsent(cacheKey, properties); + if (existing == null) { + return properties; + } else { + return existing; + } + } + } else { + // no cacheKey so we generate it with caching + return hystrixPropertiesStrategy.getThreadPoolProperties(key, builder); + } + } + + // String is CollapserKey.name() (we can't use CollapserKey directly as we can't guarantee it implements hashcode/equals correctly) + private static final ConcurrentHashMap collapserProperties = new ConcurrentHashMap(); + + /** + * Get an instance of {@link HystrixCollapserProperties} with the given factory {@link HystrixPropertiesStrategy} implementation for each {@link HystrixCollapserKey} instance. + * + * @param key + * Pass-thru to {@link HystrixPropertiesStrategy#getCollapserProperties} implementation. + * @param builder + * Pass-thru to {@link HystrixPropertiesStrategy#getCollapserProperties} implementation. + * @return {@link HystrixCollapserProperties} instance + */ + public static HystrixCollapserProperties getCollapserProperties(HystrixCollapserKey key, HystrixCollapserProperties.Setter builder) { + HystrixPropertiesStrategy hystrixPropertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); + String cacheKey = hystrixPropertiesStrategy.getCollapserPropertiesCacheKey(key, builder); + if (cacheKey != null) { + HystrixCollapserProperties properties = collapserProperties.get(cacheKey); + if (properties != null) { + return properties; + } else { + if (builder == null) { + builder = HystrixCollapserProperties.Setter(); + } + // create new instance + properties = hystrixPropertiesStrategy.getCollapserProperties(key, builder); + // cache and return + HystrixCollapserProperties existing = collapserProperties.putIfAbsent(cacheKey, properties); + if (existing == null) { + return properties; + } else { + return existing; + } + } + } else { + // no cacheKey so we generate it with caching + return hystrixPropertiesStrategy.getCollapserProperties(key, builder); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesStrategy.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesStrategy.java new file mode 100644 index 0000000..8e6dfea --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesStrategy.java @@ -0,0 +1,166 @@ +/** + * Copyright 2012 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.strategy.properties; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.HystrixTimerThreadPoolProperties; +import com.netflix.hystrix.strategy.HystrixPlugins; + +/** + * Abstract class with default implementations of factory methods for properties used by various components of Hystrix. + *

+ * See {@link HystrixPlugins} or the Hystrix GitHub Wiki for information on configuring plugins: https://github.com/Netflix/Hystrix/wiki/Plugins. + */ +public abstract class HystrixPropertiesStrategy { + + /** + * Construct an implementation of {@link HystrixCommandProperties} for {@link HystrixCommand} instances with {@link HystrixCommandKey}. + *

+ * Default Implementation + *

+ * Constructs instance of {@link HystrixPropertiesCommandDefault}. + * + * @param commandKey + * {@link HystrixCommandKey} representing the name or type of {@link HystrixCommand} + * @param builder + * {@link com.netflix.hystrix.HystrixCommandProperties.Setter} with default overrides as injected from the {@link HystrixCommand} implementation. + *

+ * The builder will return NULL for each value if no override was provided. + * @return Implementation of {@link HystrixCommandProperties} + */ + public HystrixCommandProperties getCommandProperties(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) { + return new HystrixPropertiesCommandDefault(commandKey, builder); + } + + /** + * Cache key used for caching the retrieval of {@link HystrixCommandProperties} implementations. + *

+ * Typically this would return HystrixCommandKey.name() but can be done differently if required. + *

+ * For example, null can be returned which would cause it to not cache and invoke {@link #getCommandProperties} for each {@link HystrixCommand} instantiation (not recommended). + *

+ * Default Implementation + *

+ * Returns {@link HystrixCommandKey#name()} + * + * @param commandKey command key used in determining command's cache key + * @param builder builder for {@link HystrixCommandProperties} used in determining command's cache key + * @return String value to be used as the cache key of a {@link HystrixCommandProperties} implementation. + */ + public String getCommandPropertiesCacheKey(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) { + return commandKey.name(); + } + + /** + * Construct an implementation of {@link HystrixThreadPoolProperties} for {@link HystrixThreadPool} instances with {@link HystrixThreadPoolKey}. + *

+ * Default Implementation + *

+ * Constructs instance of {@link HystrixPropertiesThreadPoolDefault}. + * + * @param threadPoolKey + * {@link HystrixThreadPoolKey} representing the name or type of {@link HystrixThreadPool} + * @param builder + * {@link com.netflix.hystrix.HystrixThreadPoolProperties.Setter} with default overrides as injected via {@link HystrixCommand} to the {@link HystrixThreadPool} implementation. + *

+ * The builder will return NULL for each value if no override was provided. + * + * @return Implementation of {@link HystrixThreadPoolProperties} + */ + public HystrixThreadPoolProperties getThreadPoolProperties(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter builder) { + return new HystrixPropertiesThreadPoolDefault(threadPoolKey, builder); + } + + /** + * Cache key used for caching the retrieval of {@link HystrixThreadPoolProperties} implementations. + *

+ * Typically this would return HystrixThreadPoolKey.name() but can be done differently if required. + *

+ * For example, null can be returned which would cause it to not cache and invoke {@link #getThreadPoolProperties} for each {@link HystrixThreadPool} instantiation (not recommended). + *

+ * Default Implementation + *

+ * Returns {@link HystrixThreadPoolKey#name()} + * + * @param threadPoolKey thread pool key used in determining thread pool's cache key + * @param builder builder for {@link HystrixThreadPoolProperties} used in determining thread pool's cache key + * @return String value to be used as the cache key of a {@link HystrixThreadPoolProperties} implementation. + */ + public String getThreadPoolPropertiesCacheKey(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter builder) { + return threadPoolKey.name(); + } + + /** + * Construct an implementation of {@link HystrixCollapserProperties} for {@link HystrixCollapser} instances with {@link HystrixCollapserKey}. + *

+ * Default Implementation + *

+ * Constructs instance of {@link HystrixPropertiesCollapserDefault}. + * + * @param collapserKey + * {@link HystrixCollapserKey} representing the name or type of {@link HystrixCollapser} + * @param builder + * {@link com.netflix.hystrix.HystrixCollapserProperties.Setter} with default overrides as injected to the {@link HystrixCollapser} implementation. + *

+ * The builder will return NULL for each value if no override was provided. + * + * @return Implementation of {@link HystrixCollapserProperties} + */ + public HystrixCollapserProperties getCollapserProperties(HystrixCollapserKey collapserKey, HystrixCollapserProperties.Setter builder) { + return new HystrixPropertiesCollapserDefault(collapserKey, builder); + } + + /** + * Cache key used for caching the retrieval of {@link HystrixCollapserProperties} implementations. + *

+ * Typically this would return HystrixCollapserKey.name() but can be done differently if required. + *

+ * For example, null can be returned which would cause it to not cache and invoke {@link #getCollapserProperties} for each {@link HystrixCollapser} instantiation (not recommended). + *

+ * Default Implementation + *

+ * Returns {@link HystrixCollapserKey#name()} + * + * @param collapserKey collapser key used in determining collapser's cache key + * @param builder builder for {@link HystrixCollapserProperties} used in determining collapser's cache key + * @return String value to be used as the cache key of a {@link HystrixCollapserProperties} implementation. + */ + public String getCollapserPropertiesCacheKey(HystrixCollapserKey collapserKey, HystrixCollapserProperties.Setter builder) { + return collapserKey.name(); + } + + /** + * Construct an implementation of {@link com.netflix.hystrix.HystrixTimerThreadPoolProperties} for configuration of the timer thread pool + * that handles timeouts and collapser logic. + *

+ * Constructs instance of {@link HystrixPropertiesTimerThreadPoolDefault}. + * + * + * @return Implementation of {@link com.netflix.hystrix.HystrixTimerThreadPoolProperties} + */ + public HystrixTimerThreadPoolProperties getTimerThreadPoolProperties() { + return new HystrixPropertiesTimerThreadPoolDefault(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesStrategyDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesStrategyDefault.java new file mode 100644 index 0000000..4a3f255 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesStrategyDefault.java @@ -0,0 +1,34 @@ +/** + * Copyright 2012 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.strategy.properties; + +/** + * Default implementation of {@link HystrixPropertiesStrategy}. + * + * @ExcludeFromJavadoc + */ +public class HystrixPropertiesStrategyDefault extends HystrixPropertiesStrategy { + + private final static HystrixPropertiesStrategyDefault INSTANCE = new HystrixPropertiesStrategyDefault(); + + private HystrixPropertiesStrategyDefault() { + } + + public static HystrixPropertiesStrategy getInstance() { + return INSTANCE; + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesThreadPoolDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesThreadPoolDefault.java new file mode 100644 index 0000000..def2ce5 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesThreadPoolDefault.java @@ -0,0 +1,32 @@ +/** + * Copyright 2012 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.strategy.properties; + +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; + +/** + * Default implementation of {@link HystrixThreadPoolProperties} using Archaius (https://github.com/Netflix/archaius) + * + * @ExcludeFromJavadoc + */ +public class HystrixPropertiesThreadPoolDefault extends HystrixThreadPoolProperties { + + protected HystrixPropertiesThreadPoolDefault(HystrixThreadPoolKey key, Setter builder) { + super(key, builder); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesTimerThreadPoolDefault.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesTimerThreadPoolDefault.java new file mode 100644 index 0000000..8e7e9d9 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesTimerThreadPoolDefault.java @@ -0,0 +1,27 @@ +/** + * Copyright 2012 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.strategy.properties; + +import com.netflix.hystrix.HystrixTimerThreadPoolProperties; + +/** + * Default implementation of {@link HystrixTimerThreadPoolProperties} using Archaius (https://github.com/Netflix/archaius) + * + * @ExcludeFromJavadoc + */ +public class HystrixPropertiesTimerThreadPoolDefault extends HystrixTimerThreadPoolProperties { + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixProperty.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixProperty.java new file mode 100644 index 0000000..1c02d43 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/HystrixProperty.java @@ -0,0 +1,180 @@ +/** + * Copyright 2012 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.strategy.properties; + +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.DynamicBooleanProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.DynamicIntegerProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.DynamicLongProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.DynamicStringProperty; + +/** + * Generic interface to represent a property value so Hystrix can consume properties without being tied to any particular backing implementation. + * + * @param + * Type of property value + */ +public interface HystrixProperty { + + public T get(); + + /** + * Helper methods for wrapping static values and dynamic Archaius (https://github.com/Netflix/archaius) properties in the {@link HystrixProperty} interface. + */ + public static class Factory { + + public static HystrixProperty asProperty(final T value) { + return new HystrixProperty() { + + @Override + public T get() { + return value; + } + + }; + } + + /** + * @ExcludeFromJavadoc + */ + public static HystrixProperty asProperty(final DynamicIntegerProperty value) { + return new HystrixProperty() { + + @Override + public Integer get() { + return value.get(); + } + + }; + } + + /** + * @ExcludeFromJavadoc + */ + public static HystrixProperty asProperty(final DynamicLongProperty value) { + return new HystrixProperty() { + + @Override + public Long get() { + return value.get(); + } + + }; + } + + /** + * @ExcludeFromJavadoc + */ + public static HystrixProperty asProperty(final DynamicStringProperty value) { + return new HystrixProperty() { + + @Override + public String get() { + return value.get(); + } + + }; + } + + /** + * @ExcludeFromJavadoc + */ + public static HystrixProperty asProperty(final DynamicBooleanProperty value) { + return new HystrixProperty() { + + @Override + public Boolean get() { + return value.get(); + } + + }; + } + + /** + * When retrieved this will return the value from the given {@link HystrixProperty} or if that returns null then return the defaultValue. + * + * @param value + * {@link HystrixProperty} of property value that can return null (meaning no value) + * @param defaultValue + * value to be returned if value returns null + * @return value or defaultValue if value returns null + */ + public static HystrixProperty asProperty(final HystrixProperty value, final T defaultValue) { + return new HystrixProperty() { + + @Override + public T get() { + T v = value.get(); + if (v == null) { + return defaultValue; + } else { + return v; + } + } + + }; + } + + /** + * When retrieved this will iterate over the contained {@link HystrixProperty} instances until a non-null value is found and return that. + * + * @param values properties to iterate over + * @return first non-null value or null if none found + */ + public static HystrixProperty asProperty(final HystrixProperty... values) { + return new HystrixProperty() { + + @Override + public T get() { + for (HystrixProperty v : values) { + // return the first one that doesn't return null + if (v.get() != null) { + return v.get(); + } + } + return null; + } + + }; + } + + /** + * @ExcludeFromJavadoc + */ + public static HystrixProperty asProperty(final HystrixPropertiesChainedArchaiusProperty.ChainLink chainedProperty) { + return new HystrixProperty() { + + @Override + public T get() { + return chainedProperty.get(); + } + + }; + } + + public static HystrixProperty nullProperty() { + return new HystrixProperty() { + + @Override + public T get() { + return null; + } + + }; + } + + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/archaius/HystrixDynamicPropertiesArchaius.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/archaius/HystrixDynamicPropertiesArchaius.java new file mode 100644 index 0000000..4c08a2e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/archaius/HystrixDynamicPropertiesArchaius.java @@ -0,0 +1,115 @@ +/** + * Copyright 2016 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.strategy.properties.archaius; + +import com.netflix.config.PropertyWrapper; +import com.netflix.hystrix.strategy.properties.HystrixDynamicProperties; +import com.netflix.hystrix.strategy.properties.HystrixDynamicProperty; + +/** + * This class should not be imported from any class in core or else Archaius will be loaded. + * @author agentgt + * @ExcludeFromJavadoc + */ +/* package */ public class HystrixDynamicPropertiesArchaius implements HystrixDynamicProperties { + + /** + * @ExcludeFromJavadoc + */ + @Override + public HystrixDynamicProperty getString(String name, String fallback) { + return new StringDynamicProperty(name, fallback); + } + /** + * @ExcludeFromJavadoc + */ + @Override + public HystrixDynamicProperty getInteger(String name, Integer fallback) { + return new IntegerDynamicProperty(name, fallback); + } + /** + * @ExcludeFromJavadoc + */ + @Override + public HystrixDynamicProperty getLong(String name, Long fallback) { + return new LongDynamicProperty(name, fallback); + } + /** + * @ExcludeFromJavadoc + */ + @Override + public HystrixDynamicProperty getBoolean(String name, Boolean fallback) { + return new BooleanDynamicProperty(name, fallback); + } + + private abstract static class ArchaiusDynamicProperty + extends PropertyWrapper implements HystrixDynamicProperty { + + protected ArchaiusDynamicProperty(String propName, T defaultValue) { + super(propName, defaultValue); + } + + @Override + public T get() { + return getValue(); + } + } + + private static class StringDynamicProperty extends ArchaiusDynamicProperty { + protected StringDynamicProperty(String propName, String defaultValue) { + super(propName, defaultValue); + } + + @Override + public String getValue() { + return prop.getString(defaultValue); + } + } + + private static class IntegerDynamicProperty extends ArchaiusDynamicProperty { + protected IntegerDynamicProperty(String propName, Integer defaultValue) { + super(propName, defaultValue); + } + + @Override + public Integer getValue() { + return prop.getInteger(defaultValue); + } + } + + private static class LongDynamicProperty extends ArchaiusDynamicProperty { + protected LongDynamicProperty(String propName, Long defaultValue) { + super(propName, defaultValue); + } + + @Override + public Long getValue() { + return prop.getLong(defaultValue); + } + } + + private static class BooleanDynamicProperty extends ArchaiusDynamicProperty { + protected BooleanDynamicProperty(String propName, Boolean defaultValue) { + super(propName, defaultValue); + } + + @Override + public Boolean getValue() { + return prop.getBoolean(defaultValue); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/package-info.java new file mode 100644 index 0000000..1b5600b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/strategy/properties/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2012 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. + */ +/** + * Strategy definition for properties and configuration and default implementation. + * + * @since 1.0.0 + */ +package com.netflix.hystrix.strategy.properties; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/ExceptionThreadingUtility.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/ExceptionThreadingUtility.java new file mode 100644 index 0000000..bdc2280 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/ExceptionThreadingUtility.java @@ -0,0 +1,30 @@ +/** + * Copyright 2012 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.util; + +@Deprecated +public class ExceptionThreadingUtility { + + @Deprecated //this functionality is no longer supported + public static void attachCallingThreadStack(Throwable e) { + //no-op now + } + + @Deprecated //this functionality is no longer supported + public static void assignCallingThread(Thread callingThread) { + //no-op now + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/Exceptions.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/Exceptions.java new file mode 100644 index 0000000..2498562 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/Exceptions.java @@ -0,0 +1,20 @@ +package com.netflix.hystrix.util; + +import java.util.LinkedList; +import java.util.List; + +public class Exceptions { + private Exceptions() { + } + + /** + * Throws the argument, return-type is RuntimeException so the caller can use a throw statement break out of the method + */ + public static RuntimeException sneakyThrow(Throwable t) { + return Exceptions.doThrow(t); + } + + private static T doThrow(Throwable ex) throws T { + throw (T) ex; + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingNumber.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingNumber.java new file mode 100644 index 0000000..fbdc35b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingNumber.java @@ -0,0 +1,676 @@ +/** + * Copyright 2012 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.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.hystrix.strategy.properties.HystrixProperty; + +/** + * A number which can be used to track counters (increment) or set values over time. + *

+ * It is "rolling" in the sense that a 'timeInMilliseconds' is given that you want to track (such as 10 seconds) and then that is broken into buckets (defaults to 10) so that the 10 second window + * doesn't empty out and restart every 10 seconds, but instead every 1 second you have a new bucket added and one dropped so that 9 of the buckets remain and only the newest starts from scratch. + *

+ * This is done so that the statistics are gathered over a rolling 10 second window with data being added/dropped in 1 second intervals (or whatever granularity is defined by the arguments) rather + * than each 10 second window starting at 0 again. + *

+ * Performance-wise this class is optimized for writes, not reads. This is done because it expects far higher write volume (thousands/second) than reads (a few per second). + *

+ * For example, on each read to getSum/getCount it will iterate buckets to sum the data so that on writes we don't need to maintain the overall sum and pay the synchronization cost at each write to + * ensure the sum is up-to-date when the read can easily iterate each bucket to get the sum when it needs it. + *

+ * See UnitTest for usage and expected behavior examples. + * + * @ThreadSafe + */ +public class HystrixRollingNumber { + + private static final Time ACTUAL_TIME = new ActualTime(); + private final Time time; + final int timeInMilliseconds; + final int numberOfBuckets; + final int bucketSizeInMillseconds; + + final BucketCircularArray buckets; + private final CumulativeSum cumulativeSum = new CumulativeSum(); + + /** + * Construct a counter, with configurable properties for how many buckets, and how long of an interval to track + * @param timeInMilliseconds length of time to report metrics over + * @param numberOfBuckets number of buckets to use + * + * @deprecated Please use {@link HystrixRollingNumber(int, int) instead}. These values are no longer allowed to + * be updated at runtime. + */ + @Deprecated + public HystrixRollingNumber(HystrixProperty timeInMilliseconds, HystrixProperty numberOfBuckets) { + this(timeInMilliseconds.get(), numberOfBuckets.get()); + } + + public HystrixRollingNumber(int timeInMilliseconds, int numberOfBuckets) { + this(ACTUAL_TIME, timeInMilliseconds, numberOfBuckets); + } + + /* package for testing */ HystrixRollingNumber(Time time, int timeInMilliseconds, int numberOfBuckets) { + this.time = time; + this.timeInMilliseconds = timeInMilliseconds; + this.numberOfBuckets = numberOfBuckets; + + if (timeInMilliseconds % numberOfBuckets != 0) { + throw new IllegalArgumentException("The timeInMilliseconds must divide equally into numberOfBuckets. For example 1000/10 is ok, 1000/11 is not."); + } + this.bucketSizeInMillseconds = timeInMilliseconds / numberOfBuckets; + + buckets = new BucketCircularArray(numberOfBuckets); + } + + /** + * Increment the counter in the current bucket by one for the given {@link HystrixRollingNumberEvent} type. + *

+ * The {@link HystrixRollingNumberEvent} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to increment + */ + public void increment(HystrixRollingNumberEvent type) { + getCurrentBucket().getAdder(type).increment(); + } + + /** + * Add to the counter in the current bucket for the given {@link HystrixRollingNumberEvent} type. + *

+ * The {@link HystrixRollingNumberEvent} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to add to + * @param value + * long value to be added to the current bucket + */ + public void add(HystrixRollingNumberEvent type, long value) { + getCurrentBucket().getAdder(type).add(value); + } + + /** + * Update a value and retain the max value. + *

+ * The {@link HystrixRollingNumberEvent} must be a "max updater" type HystrixRollingNumberEvent.isMaxUpdater() == true. + * + * @param type HystrixRollingNumberEvent defining which counter to retrieve values from + * @param value long value to be given to the max updater + */ + public void updateRollingMax(HystrixRollingNumberEvent type, long value) { + getCurrentBucket().getMaxUpdater(type).update(value); + } + + /** + * Force a reset of all rolling counters (clear all buckets) so that statistics start being gathered from scratch. + *

+ * This does NOT reset the CumulativeSum values. + */ + public void reset() { + // if we are resetting, that means the lastBucket won't have a chance to be captured in CumulativeSum, so let's do it here + Bucket lastBucket = buckets.peekLast(); + if (lastBucket != null) { + cumulativeSum.addBucket(lastBucket); + } + + // clear buckets so we start over again + buckets.clear(); + } + + /** + * Get the cumulative sum of all buckets ever since the JVM started without rolling for the given {@link HystrixRollingNumberEvent} type. + *

+ * See {@link #getRollingSum(HystrixRollingNumberEvent)} for the rolling sum. + *

+ * The {@link HystrixRollingNumberEvent} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type HystrixRollingNumberEvent defining which counter to retrieve values from + * @return cumulative sum of all increments and adds for the given {@link HystrixRollingNumberEvent} counter type + */ + public long getCumulativeSum(HystrixRollingNumberEvent type) { + // this isn't 100% atomic since multiple threads can be affecting latestBucket & cumulativeSum independently + // but that's okay since the count is always a moving target and we're accepting a "point in time" best attempt + // we are however putting 'getValueOfLatestBucket' first since it can have side-affects on cumulativeSum whereas the inverse is not true + return getValueOfLatestBucket(type) + cumulativeSum.get(type); + } + + /** + * Get the sum of all buckets in the rolling counter for the given {@link HystrixRollingNumberEvent} type. + *

+ * The {@link HystrixRollingNumberEvent} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to retrieve values from + * @return + * value from the given {@link HystrixRollingNumberEvent} counter type + */ + public long getRollingSum(HystrixRollingNumberEvent type) { + Bucket lastBucket = getCurrentBucket(); + if (lastBucket == null) + return 0; + + long sum = 0; + for (Bucket b : buckets) { + sum += b.getAdder(type).sum(); + } + return sum; + } + + /** + * Get the value of the latest (current) bucket in the rolling counter for the given {@link HystrixRollingNumberEvent} type. + *

+ * The {@link HystrixRollingNumberEvent} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to retrieve value from + * @return + * value from latest bucket for given {@link HystrixRollingNumberEvent} counter type + */ + public long getValueOfLatestBucket(HystrixRollingNumberEvent type) { + Bucket lastBucket = getCurrentBucket(); + if (lastBucket == null) + return 0; + // we have bucket data so we'll return the lastBucket + return lastBucket.get(type); + } + + /** + * Get an array of values for all buckets in the rolling counter for the given {@link HystrixRollingNumberEvent} type. + *

+ * Index 0 is the oldest bucket. + *

+ * The {@link HystrixRollingNumberEvent} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to retrieve values from + * @return array of values from each of the rolling buckets for given {@link HystrixRollingNumberEvent} counter type + */ + public long[] getValues(HystrixRollingNumberEvent type) { + Bucket lastBucket = getCurrentBucket(); + if (lastBucket == null) + return new long[0]; + + // get buckets as an array (which is a copy of the current state at this point in time) + Bucket[] bucketArray = buckets.getArray(); + + // we have bucket data so we'll return an array of values for all buckets + long values[] = new long[bucketArray.length]; + int i = 0; + for (Bucket bucket : bucketArray) { + if (type.isCounter()) { + values[i++] = bucket.getAdder(type).sum(); + } else if (type.isMaxUpdater()) { + values[i++] = bucket.getMaxUpdater(type).max(); + } + } + return values; + } + + /** + * Get the max value of values in all buckets for the given {@link HystrixRollingNumberEvent} type. + *

+ * The {@link HystrixRollingNumberEvent} must be a "max updater" type HystrixRollingNumberEvent.isMaxUpdater() == true. + * + * @param type + * HystrixRollingNumberEvent defining which "max updater" to retrieve values from + * @return max value for given {@link HystrixRollingNumberEvent} type during rolling window + */ + public long getRollingMaxValue(HystrixRollingNumberEvent type) { + long values[] = getValues(type); + if (values.length == 0) { + return 0; + } else { + Arrays.sort(values); + return values[values.length - 1]; + } + } + + private ReentrantLock newBucketLock = new ReentrantLock(); + + /* package for testing */Bucket getCurrentBucket() { + long currentTime = time.getCurrentTimeInMillis(); + + /* a shortcut to try and get the most common result of immediately finding the current bucket */ + + /** + * Retrieve the latest bucket if the given time is BEFORE the end of the bucket window, otherwise it returns NULL. + * + * NOTE: This is thread-safe because it's accessing 'buckets' which is a LinkedBlockingDeque + */ + Bucket currentBucket = buckets.peekLast(); + if (currentBucket != null && currentTime < currentBucket.windowStart + this.bucketSizeInMillseconds) { + // if we're within the bucket 'window of time' return the current one + // NOTE: We do not worry if we are BEFORE the window in a weird case of where thread scheduling causes that to occur, + // we'll just use the latest as long as we're not AFTER the window + return currentBucket; + } + + /* if we didn't find the current bucket above, then we have to create one */ + + /** + * The following needs to be synchronized/locked even with a synchronized/thread-safe data structure such as LinkedBlockingDeque because + * the logic involves multiple steps to check existence, create an object then insert the object. The 'check' or 'insertion' themselves + * are thread-safe by themselves but not the aggregate algorithm, thus we put this entire block of logic inside synchronized. + * + * I am using a tryLock if/then (http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/Lock.html#tryLock()) + * so that a single thread will get the lock and as soon as one thread gets the lock all others will go the 'else' block + * and just return the currentBucket until the newBucket is created. This should allow the throughput to be far higher + * and only slow down 1 thread instead of blocking all of them in each cycle of creating a new bucket based on some testing + * (and it makes sense that it should as well). + * + * This means the timing won't be exact to the millisecond as to what data ends up in a bucket, but that's acceptable. + * It's not critical to have exact precision to the millisecond, as long as it's rolling, if we can instead reduce the impact synchronization. + * + * More importantly though it means that the 'if' block within the lock needs to be careful about what it changes that can still + * be accessed concurrently in the 'else' block since we're not completely synchronizing access. + * + * For example, we can't have a multi-step process to add a bucket, remove a bucket, then update the sum since the 'else' block of code + * can retrieve the sum while this is all happening. The trade-off is that we don't maintain the rolling sum and let readers just iterate + * bucket to calculate the sum themselves. This is an example of favoring write-performance instead of read-performance and how the tryLock + * versus a synchronized block needs to be accommodated. + */ + if (newBucketLock.tryLock()) { + try { + if (buckets.peekLast() == null) { + // the list is empty so create the first bucket + Bucket newBucket = new Bucket(currentTime); + buckets.addLast(newBucket); + return newBucket; + } else { + // We go into a loop so that it will create as many buckets as needed to catch up to the current time + // as we want the buckets complete even if we don't have transactions during a period of time. + for (int i = 0; i < numberOfBuckets; i++) { + // we have at least 1 bucket so retrieve it + Bucket lastBucket = buckets.peekLast(); + if (currentTime < lastBucket.windowStart + this.bucketSizeInMillseconds) { + // if we're within the bucket 'window of time' return the current one + // NOTE: We do not worry if we are BEFORE the window in a weird case of where thread scheduling causes that to occur, + // we'll just use the latest as long as we're not AFTER the window + return lastBucket; + } else if (currentTime - (lastBucket.windowStart + this.bucketSizeInMillseconds) > timeInMilliseconds) { + // the time passed is greater than the entire rolling counter so we want to clear it all and start from scratch + reset(); + // recursively call getCurrentBucket which will create a new bucket and return it + return getCurrentBucket(); + } else { // we're past the window so we need to create a new bucket + // create a new bucket and add it as the new 'last' + buckets.addLast(new Bucket(lastBucket.windowStart + this.bucketSizeInMillseconds)); + // add the lastBucket values to the cumulativeSum + cumulativeSum.addBucket(lastBucket); + } + } + // we have finished the for-loop and created all of the buckets, so return the lastBucket now + return buckets.peekLast(); + } + } finally { + newBucketLock.unlock(); + } + } else { + currentBucket = buckets.peekLast(); + if (currentBucket != null) { + // we didn't get the lock so just return the latest bucket while another thread creates the next one + return currentBucket; + } else { + // the rare scenario where multiple threads raced to create the very first bucket + // wait slightly and then use recursion while the other thread finishes creating a bucket + try { + Thread.sleep(5); + } catch (Exception e) { + // ignore + } + return getCurrentBucket(); + } + } + } + + /* package */static interface Time { + public long getCurrentTimeInMillis(); + } + + private static class ActualTime implements Time { + + @Override + public long getCurrentTimeInMillis() { + return System.currentTimeMillis(); + } + + } + + /** + * Counters for a given 'bucket' of time. + */ + /* package */static class Bucket { + final long windowStart; + final LongAdder[] adderForCounterType; + final LongMaxUpdater[] updaterForCounterType; + + Bucket(long startTime) { + this.windowStart = startTime; + + /* + * We support both LongAdder and LongMaxUpdater in a bucket but don't want the memory allocation + * of all types for each so we only allocate the objects if the HystrixRollingNumberEvent matches + * the correct type - though we still have the allocation of empty arrays to the given length + * as we want to keep using the type.ordinal() value for fast random access. + */ + + // initialize the array of LongAdders + adderForCounterType = new LongAdder[HystrixRollingNumberEvent.values().length]; + for (HystrixRollingNumberEvent type : HystrixRollingNumberEvent.values()) { + if (type.isCounter()) { + adderForCounterType[type.ordinal()] = new LongAdder(); + } + } + + updaterForCounterType = new LongMaxUpdater[HystrixRollingNumberEvent.values().length]; + for (HystrixRollingNumberEvent type : HystrixRollingNumberEvent.values()) { + if (type.isMaxUpdater()) { + updaterForCounterType[type.ordinal()] = new LongMaxUpdater(); + // initialize to 0 otherwise it is Long.MIN_VALUE + updaterForCounterType[type.ordinal()].update(0); + } + } + } + + long get(HystrixRollingNumberEvent type) { + if (type.isCounter()) { + return adderForCounterType[type.ordinal()].sum(); + } + if (type.isMaxUpdater()) { + return updaterForCounterType[type.ordinal()].max(); + } + throw new IllegalStateException("Unknown type of event: " + type.name()); + } + + LongAdder getAdder(HystrixRollingNumberEvent type) { + if (!type.isCounter()) { + throw new IllegalStateException("Type is not a Counter: " + type.name()); + } + return adderForCounterType[type.ordinal()]; + } + + LongMaxUpdater getMaxUpdater(HystrixRollingNumberEvent type) { + if (!type.isMaxUpdater()) { + throw new IllegalStateException("Type is not a MaxUpdater: " + type.name()); + } + return updaterForCounterType[type.ordinal()]; + } + + } + + /** + * Cumulative counters (from start of JVM) from each Type + */ + /* package */static class CumulativeSum { + final LongAdder[] adderForCounterType; + final LongMaxUpdater[] updaterForCounterType; + + CumulativeSum() { + + /* + * We support both LongAdder and LongMaxUpdater in a bucket but don't want the memory allocation + * of all types for each so we only allocate the objects if the HystrixRollingNumberEvent matches + * the correct type - though we still have the allocation of empty arrays to the given length + * as we want to keep using the type.ordinal() value for fast random access. + */ + + // initialize the array of LongAdders + adderForCounterType = new LongAdder[HystrixRollingNumberEvent.values().length]; + for (HystrixRollingNumberEvent type : HystrixRollingNumberEvent.values()) { + if (type.isCounter()) { + adderForCounterType[type.ordinal()] = new LongAdder(); + } + } + + updaterForCounterType = new LongMaxUpdater[HystrixRollingNumberEvent.values().length]; + for (HystrixRollingNumberEvent type : HystrixRollingNumberEvent.values()) { + if (type.isMaxUpdater()) { + updaterForCounterType[type.ordinal()] = new LongMaxUpdater(); + // initialize to 0 otherwise it is Long.MIN_VALUE + updaterForCounterType[type.ordinal()].update(0); + } + } + } + + public void addBucket(Bucket lastBucket) { + for (HystrixRollingNumberEvent type : HystrixRollingNumberEvent.values()) { + if (type.isCounter()) { + getAdder(type).add(lastBucket.getAdder(type).sum()); + } + if (type.isMaxUpdater()) { + getMaxUpdater(type).update(lastBucket.getMaxUpdater(type).max()); + } + } + } + + long get(HystrixRollingNumberEvent type) { + if (type.isCounter()) { + return adderForCounterType[type.ordinal()].sum(); + } + if (type.isMaxUpdater()) { + return updaterForCounterType[type.ordinal()].max(); + } + throw new IllegalStateException("Unknown type of event: " + type.name()); + } + + LongAdder getAdder(HystrixRollingNumberEvent type) { + if (!type.isCounter()) { + throw new IllegalStateException("Type is not a Counter: " + type.name()); + } + return adderForCounterType[type.ordinal()]; + } + + LongMaxUpdater getMaxUpdater(HystrixRollingNumberEvent type) { + if (!type.isMaxUpdater()) { + throw new IllegalStateException("Type is not a MaxUpdater: " + type.name()); + } + return updaterForCounterType[type.ordinal()]; + } + + } + + /** + * This is a circular array acting as a FIFO queue. + *

+ * It purposefully does NOT implement Deque or some other Collection interface as it only implements functionality necessary for this RollingNumber use case. + *

+ * Important Thread-Safety Note: This is ONLY thread-safe within the context of RollingNumber and the protection it gives in the getCurrentBucket method. It uses AtomicReference + * objects to ensure anything done outside of getCurrentBucket is thread-safe, and to ensure visibility of changes across threads (ie. volatility) but the addLast and removeFirst + * methods are NOT thread-safe for external access they depend upon the lock.tryLock() protection in getCurrentBucket which ensures only a single thread will access them at at time. + *

+ * benjchristensen => This implementation was chosen based on performance testing I did and documented at: http://benjchristensen.com/2011/10/08/atomiccirculararray/ + */ + /* package */static class BucketCircularArray implements Iterable { + private final AtomicReference state; + private final int dataLength; // we don't resize, we always stay the same, so remember this + private final int numBuckets; + + /** + * Immutable object that is atomically set every time the state of the BucketCircularArray changes + *

+ * This handles the compound operations + */ + private class ListState { + /* + * this is an AtomicReferenceArray and not a normal Array because we're copying the reference + * between ListState objects and multiple threads could maintain references across these + * compound operations so I want the visibility/concurrency guarantees + */ + private final AtomicReferenceArray data; + private final int size; + private final int tail; + private final int head; + + private ListState(AtomicReferenceArray data, int head, int tail) { + this.head = head; + this.tail = tail; + if (head == 0 && tail == 0) { + size = 0; + } else { + this.size = (tail + dataLength - head) % dataLength; + } + this.data = data; + } + + public Bucket tail() { + if (size == 0) { + return null; + } else { + // we want to get the last item, so size()-1 + return data.get(convert(size - 1)); + } + } + + private Bucket[] getArray() { + /* + * this isn't technically thread-safe since it requires multiple reads on something that can change + * but since we never clear the data directly, only increment/decrement head/tail we would never get a NULL + * just potentially return stale data which we are okay with doing + */ + ArrayList array = new ArrayList(); + for (int i = 0; i < size; i++) { + array.add(data.get(convert(i))); + } + return array.toArray(new Bucket[array.size()]); + } + + private ListState incrementTail() { + /* if incrementing results in growing larger than 'length' which is the max we should be at, then also increment head (equivalent of removeFirst but done atomically) */ + if (size == numBuckets) { + // increment tail and head + return new ListState(data, (head + 1) % dataLength, (tail + 1) % dataLength); + } else { + // increment only tail + return new ListState(data, head, (tail + 1) % dataLength); + } + } + + public ListState clear() { + return new ListState(new AtomicReferenceArray(dataLength), 0, 0); + } + + public ListState addBucket(Bucket b) { + /* + * We could in theory have 2 threads addBucket concurrently and this compound operation would interleave. + *

+ * This should NOT happen since getCurrentBucket is supposed to be executed by a single thread. + *

+ * If it does happen, it's not a huge deal as incrementTail() will be protected by compareAndSet and one of the two addBucket calls will succeed with one of the Buckets. + *

+ * In either case, a single Bucket will be returned as "last" and data loss should not occur and everything keeps in sync for head/tail. + *

+ * Also, it's fine to set it before incrementTail because nothing else should be referencing that index position until incrementTail occurs. + */ + data.set(tail, b); + return incrementTail(); + } + + // The convert() method takes a logical index (as if head was + // always 0) and calculates the index within elementData + private int convert(int index) { + return (index + head) % dataLength; + } + } + + BucketCircularArray(int size) { + AtomicReferenceArray _buckets = new AtomicReferenceArray(size + 1); // + 1 as extra room for the add/remove; + state = new AtomicReference(new ListState(_buckets, 0, 0)); + dataLength = _buckets.length(); + numBuckets = size; + } + + public void clear() { + while (true) { + /* + * it should be very hard to not succeed the first pass thru since this is typically is only called from + * a single thread protected by a tryLock, but there is at least 1 other place (at time of writing this comment) + * where reset can be called from (CircuitBreaker.markSuccess after circuit was tripped) so it can + * in an edge-case conflict. + * + * Instead of trying to determine if someone already successfully called clear() and we should skip + * we will have both calls reset the circuit, even if that means losing data added in between the two + * depending on thread scheduling. + * + * The rare scenario in which that would occur, we'll accept the possible data loss while clearing it + * since the code has stated its desire to clear() anyways. + */ + ListState current = state.get(); + ListState newState = current.clear(); + if (state.compareAndSet(current, newState)) { + return; + } + } + } + + /** + * Returns an iterator on a copy of the internal array so that the iterator won't fail by buckets being added/removed concurrently. + */ + public Iterator iterator() { + return Collections.unmodifiableList(Arrays.asList(getArray())).iterator(); + } + + public void addLast(Bucket o) { + ListState currentState = state.get(); + // create new version of state (what we want it to become) + ListState newState = currentState.addBucket(o); + + /* + * use compareAndSet to set in case multiple threads are attempting (which shouldn't be the case because since addLast will ONLY be called by a single thread at a time due to protection + * provided in getCurrentBucket) + */ + if (state.compareAndSet(currentState, newState)) { + // we succeeded + return; + } else { + // we failed, someone else was adding or removing + // instead of trying again and risking multiple addLast concurrently (which shouldn't be the case) + // we'll just return and let the other thread 'win' and if the timing is off the next call to getCurrentBucket will fix things + return; + } + } + + public Bucket getLast() { + return peekLast(); + } + + public int size() { + // the size can also be worked out each time as: + // return (tail + data.length() - head) % data.length(); + return state.get().size; + } + + public Bucket peekLast() { + return state.get().tail(); + } + + private Bucket[] getArray() { + return state.get().getArray(); + } + + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingNumberEvent.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingNumberEvent.java new file mode 100644 index 0000000..78c732f --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingNumberEvent.java @@ -0,0 +1,75 @@ +/** + * Copyright 2012 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.util; + +import com.netflix.hystrix.HystrixEventType; + +/** + * Various states/events that can be captured in the {@link HystrixRollingNumber}. + *

+ * Note that events are defined as different types: + *

    + *
  • Counter: isCounter() == true
  • + *
  • MaxUpdater: isMaxUpdater() == true
  • + *
+ *

+ * The Counter type events can be used with {@link HystrixRollingNumber#increment}, {@link HystrixRollingNumber#add}, {@link HystrixRollingNumber#getRollingSum} and others. + *

+ * The MaxUpdater type events can be used with {@link HystrixRollingNumber#updateRollingMax} and {@link HystrixRollingNumber#getRollingMaxValue}. + */ +public enum HystrixRollingNumberEvent { + SUCCESS(1), FAILURE(1), TIMEOUT(1), SHORT_CIRCUITED(1), THREAD_POOL_REJECTED(1), SEMAPHORE_REJECTED(1), BAD_REQUEST(1), + FALLBACK_SUCCESS(1), FALLBACK_FAILURE(1), FALLBACK_REJECTION(1), FALLBACK_DISABLED(1), FALLBACK_MISSING(1), EXCEPTION_THROWN(1), COMMAND_MAX_ACTIVE(2), EMIT(1), FALLBACK_EMIT(1), + THREAD_EXECUTION(1), THREAD_MAX_ACTIVE(2), COLLAPSED(1), RESPONSE_FROM_CACHE(1), + COLLAPSER_REQUEST_BATCHED(1), COLLAPSER_BATCH(1); + + private final int type; + + private HystrixRollingNumberEvent(int type) { + this.type = type; + } + + public boolean isCounter() { + return type == 1; + } + + public boolean isMaxUpdater() { + return type == 2; + } + + public static HystrixRollingNumberEvent from(HystrixEventType eventType) { + switch (eventType) { + case BAD_REQUEST: return HystrixRollingNumberEvent.BAD_REQUEST; + case COLLAPSED: return HystrixRollingNumberEvent.COLLAPSED; + case EMIT: return HystrixRollingNumberEvent.EMIT; + case EXCEPTION_THROWN: return HystrixRollingNumberEvent.EXCEPTION_THROWN; + case FAILURE: return HystrixRollingNumberEvent.FAILURE; + case FALLBACK_EMIT: return HystrixRollingNumberEvent.FALLBACK_EMIT; + case FALLBACK_FAILURE: return HystrixRollingNumberEvent.FALLBACK_FAILURE; + case FALLBACK_DISABLED: return HystrixRollingNumberEvent.FALLBACK_DISABLED; + case FALLBACK_MISSING: return HystrixRollingNumberEvent.FALLBACK_MISSING; + case FALLBACK_REJECTION: return HystrixRollingNumberEvent.FALLBACK_REJECTION; + case FALLBACK_SUCCESS: return HystrixRollingNumberEvent.FALLBACK_SUCCESS; + case RESPONSE_FROM_CACHE: return HystrixRollingNumberEvent.RESPONSE_FROM_CACHE; + case SEMAPHORE_REJECTED: return HystrixRollingNumberEvent.SEMAPHORE_REJECTED; + case SHORT_CIRCUITED: return HystrixRollingNumberEvent.SHORT_CIRCUITED; + case SUCCESS: return HystrixRollingNumberEvent.SUCCESS; + case THREAD_POOL_REJECTED: return HystrixRollingNumberEvent.THREAD_POOL_REJECTED; + case TIMEOUT: return HystrixRollingNumberEvent.TIMEOUT; + default: throw new RuntimeException("Unknown HystrixEventType : " + eventType); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingPercentile.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingPercentile.java new file mode 100644 index 0000000..8ab1ade --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixRollingPercentile.java @@ -0,0 +1,651 @@ +/** + * Copyright 2012 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.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerArray; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.hystrix.strategy.properties.HystrixProperty; + +/** + * Add values to a rolling window and retrieve percentile calculations such as median, 90th, 99th, etc. + *

+ * The underlying data structure contains a circular array of buckets that "roll" over time. + *

+ * For example, if the time window is configured to 60 seconds with 12 buckets of 5 seconds each, values will be captured in each 5 second bucket and rotate each 5 seconds. + *

+ * This means that percentile calculations are for the "rolling window" of 55-60 seconds up to 5 seconds ago. + *

+ * Each bucket will contain a circular array of long values and if more than the configured amount (1000 values for example) it will wrap around and overwrite values until time passes and a new bucket + * is allocated. This sampling approach for high volume metrics is done to conserve memory and reduce sorting time when calculating percentiles. + */ +public class HystrixRollingPercentile { + + private static final Logger logger = LoggerFactory.getLogger(HystrixRollingPercentile.class); + + private static final Time ACTUAL_TIME = new ActualTime(); + private final Time time; + /* package for testing */ final BucketCircularArray buckets; + private final int timeInMilliseconds; + private final int numberOfBuckets; + private final int bucketDataLength; + private final int bucketSizeInMilliseconds; + private final HystrixProperty enabled; + + /* + * This will get flipped each time a new bucket is created. + */ + /* package for testing */ volatile PercentileSnapshot currentPercentileSnapshot = new PercentileSnapshot(0); + + /** + * + * @param timeInMilliseconds + * {@code HystrixProperty} for number of milliseconds of data that should be tracked + * Note that this value is represented as a {@link HystrixProperty}, but can not actually be modified + * at runtime, to avoid data loss + *

+ * Example: 60000 for 1 minute + * @param numberOfBuckets + * {@code HystrixProperty} for number of buckets that the time window should be divided into + * Note that this value is represented as a {@link HystrixProperty}, but can not actually be modified + * at runtime, to avoid data loss + *

+ * Example: 12 for 5 second buckets in a 1 minute window + * @param bucketDataLength + * {@code HystrixProperty} for number of values stored in each bucket + * Note that this value is represented as a {@link HystrixProperty}, but can not actually be modified + * at runtime, to avoid data loss + *

+ * Example: 1000 to store a max of 1000 values in each 5 second bucket + * @param enabled + * {@code HystrixProperty} whether data should be tracked and percentiles calculated. + *

+ * If 'false' methods will do nothing. + * @deprecated Please use the constructor with non-configurable properties {@link HystrixRollingPercentile(Time, int, int, int, HystrixProperty} + */ + @Deprecated + public HystrixRollingPercentile(HystrixProperty timeInMilliseconds, HystrixProperty numberOfBuckets, HystrixProperty bucketDataLength, HystrixProperty enabled) { + this(timeInMilliseconds.get(), numberOfBuckets.get(), bucketDataLength.get(), enabled); + } + + /** + * + * @param timeInMilliseconds + * number of milliseconds of data that should be tracked + *

+ * Example: 60000 for 1 minute + * @param numberOfBuckets + * number of buckets that the time window should be divided into + *

+ * Example: 12 for 5 second buckets in a 1 minute window + * @param bucketDataLength + * number of values stored in each bucket + *

+ * Example: 1000 to store a max of 1000 values in each 5 second bucket + * @param enabled + * {@code HystrixProperty} whether data should be tracked and percentiles calculated. + *

+ * If 'false' methods will do nothing. + */ + public HystrixRollingPercentile(int timeInMilliseconds, int numberOfBuckets, int bucketDataLength, HystrixProperty enabled) { + this(ACTUAL_TIME, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); + + } + + /* package for testing */ HystrixRollingPercentile(Time time, int timeInMilliseconds, int numberOfBuckets, int bucketDataLength, HystrixProperty enabled) { + this.time = time; + this.timeInMilliseconds = timeInMilliseconds; + this.numberOfBuckets = numberOfBuckets; + this.bucketDataLength = bucketDataLength; + this.enabled = enabled; + + if (this.timeInMilliseconds % this.numberOfBuckets != 0) { + throw new IllegalArgumentException("The timeInMilliseconds must divide equally into numberOfBuckets. For example 1000/10 is ok, 1000/11 is not."); + } + this.bucketSizeInMilliseconds = this.timeInMilliseconds / this.numberOfBuckets; + + buckets = new BucketCircularArray(this.numberOfBuckets); + } + + /** + * Add value (or values) to current bucket. + * + * @param value + * Value to be stored in current bucket such as execution latency in milliseconds + */ + public void addValue(int... value) { + /* no-op if disabled */ + if (!enabled.get()) + return; + + for (int v : value) { + try { + getCurrentBucket().data.addValue(v); + } catch (Exception e) { + logger.error("Failed to add value: " + v, e); + } + } + } + + /** + * Compute a percentile from the underlying rolling buckets of values. + *

+ * For performance reasons it maintains a single snapshot of the sorted values from all buckets that is re-generated each time the bucket rotates. + *

+ * This means that if a bucket is 5000ms, then this method will re-compute a percentile at most once every 5000ms. + * + * @param percentile + * value such as 99 (99th percentile), 99.5 (99.5th percentile), 50 (median, 50th percentile) to compute and retrieve percentile from rolling buckets. + * @return int percentile value + */ + public int getPercentile(double percentile) { + /* no-op if disabled */ + if (!enabled.get()) + return -1; + + // force logic to move buckets forward in case other requests aren't making it happen + getCurrentBucket(); + // fetch the current snapshot + return getCurrentPercentileSnapshot().getPercentile(percentile); + } + + /** + * This returns the mean (average) of all values in the current snapshot. This is not a percentile but often desired so captured and exposed here. + * + * @return mean of all values + */ + public int getMean() { + /* no-op if disabled */ + if (!enabled.get()) + return -1; + + // force logic to move buckets forward in case other requests aren't making it happen + getCurrentBucket(); + // fetch the current snapshot + return getCurrentPercentileSnapshot().getMean(); + } + + /** + * This will retrieve the current snapshot or create a new one if one does not exist. + *

+ * It will NOT include data from the current bucket, but all previous buckets. + *

+ * It remains cached until the next bucket rotates at which point a new one will be created. + */ + private PercentileSnapshot getCurrentPercentileSnapshot() { + return currentPercentileSnapshot; + } + + private ReentrantLock newBucketLock = new ReentrantLock(); + + private Bucket getCurrentBucket() { + long currentTime = time.getCurrentTimeInMillis(); + + /* a shortcut to try and get the most common result of immediately finding the current bucket */ + + /** + * Retrieve the latest bucket if the given time is BEFORE the end of the bucket window, otherwise it returns NULL. + * + * NOTE: This is thread-safe because it's accessing 'buckets' which is a LinkedBlockingDeque + */ + Bucket currentBucket = buckets.peekLast(); + if (currentBucket != null && currentTime < currentBucket.windowStart + this.bucketSizeInMilliseconds) { + // if we're within the bucket 'window of time' return the current one + // NOTE: We do not worry if we are BEFORE the window in a weird case of where thread scheduling causes that to occur, + // we'll just use the latest as long as we're not AFTER the window + return currentBucket; + } + + /* if we didn't find the current bucket above, then we have to create one */ + + /** + * The following needs to be synchronized/locked even with a synchronized/thread-safe data structure such as LinkedBlockingDeque because + * the logic involves multiple steps to check existence, create an object then insert the object. The 'check' or 'insertion' themselves + * are thread-safe by themselves but not the aggregate algorithm, thus we put this entire block of logic inside synchronized. + * + * I am using a tryLock if/then (http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/Lock.html#tryLock()) + * so that a single thread will get the lock and as soon as one thread gets the lock all others will go the 'else' block + * and just return the currentBucket until the newBucket is created. This should allow the throughput to be far higher + * and only slow down 1 thread instead of blocking all of them in each cycle of creating a new bucket based on some testing + * (and it makes sense that it should as well). + * + * This means the timing won't be exact to the millisecond as to what data ends up in a bucket, but that's acceptable. + * It's not critical to have exact precision to the millisecond, as long as it's rolling, if we can instead reduce the impact synchronization. + * + * More importantly though it means that the 'if' block within the lock needs to be careful about what it changes that can still + * be accessed concurrently in the 'else' block since we're not completely synchronizing access. + * + * For example, we can't have a multi-step process to add a bucket, remove a bucket, then update the sum since the 'else' block of code + * can retrieve the sum while this is all happening. The trade-off is that we don't maintain the rolling sum and let readers just iterate + * bucket to calculate the sum themselves. This is an example of favoring write-performance instead of read-performance and how the tryLock + * versus a synchronized block needs to be accommodated. + */ + if (newBucketLock.tryLock()) { + try { + if (buckets.peekLast() == null) { + // the list is empty so create the first bucket + Bucket newBucket = new Bucket(currentTime, bucketDataLength); + buckets.addLast(newBucket); + return newBucket; + } else { + // We go into a loop so that it will create as many buckets as needed to catch up to the current time + // as we want the buckets complete even if we don't have transactions during a period of time. + for (int i = 0; i < numberOfBuckets; i++) { + // we have at least 1 bucket so retrieve it + Bucket lastBucket = buckets.peekLast(); + if (currentTime < lastBucket.windowStart + this.bucketSizeInMilliseconds) { + // if we're within the bucket 'window of time' return the current one + // NOTE: We do not worry if we are BEFORE the window in a weird case of where thread scheduling causes that to occur, + // we'll just use the latest as long as we're not AFTER the window + return lastBucket; + } else if (currentTime - (lastBucket.windowStart + this.bucketSizeInMilliseconds) > timeInMilliseconds) { + // the time passed is greater than the entire rolling counter so we want to clear it all and start from scratch + reset(); + // recursively call getCurrentBucket which will create a new bucket and return it + return getCurrentBucket(); + } else { // we're past the window so we need to create a new bucket + Bucket[] allBuckets = buckets.getArray(); + // create a new bucket and add it as the new 'last' (once this is done other threads will start using it on subsequent retrievals) + buckets.addLast(new Bucket(lastBucket.windowStart + this.bucketSizeInMilliseconds, bucketDataLength)); + // we created a new bucket so let's re-generate the PercentileSnapshot (not including the new bucket) + currentPercentileSnapshot = new PercentileSnapshot(allBuckets); + } + } + // we have finished the for-loop and created all of the buckets, so return the lastBucket now + return buckets.peekLast(); + } + } finally { + newBucketLock.unlock(); + } + } else { + currentBucket = buckets.peekLast(); + if (currentBucket != null) { + // we didn't get the lock so just return the latest bucket while another thread creates the next one + return currentBucket; + } else { + // the rare scenario where multiple threads raced to create the very first bucket + // wait slightly and then use recursion while the other thread finishes creating a bucket + try { + Thread.sleep(5); + } catch (Exception e) { + // ignore + } + return getCurrentBucket(); + } + } + } + + /** + * Force a reset so that percentiles start being gathered from scratch. + */ + public void reset() { + /* no-op if disabled */ + if (!enabled.get()) + return; + + // clear buckets so we start over again + buckets.clear(); + + // and also make sure the percentile snapshot gets reset + currentPercentileSnapshot = new PercentileSnapshot(buckets.getArray()); + } + + /* package-private for testing */ static class PercentileBucketData { + private final int length; + private final AtomicIntegerArray list; + private final AtomicInteger index = new AtomicInteger(); + + public PercentileBucketData(int dataLength) { + this.length = dataLength; + this.list = new AtomicIntegerArray(dataLength); + } + + public void addValue(int... latency) { + for (int l : latency) { + /* We just wrap around the beginning and over-write if we go past 'dataLength' as that will effectively cause us to "sample" the most recent data */ + list.set(index.getAndIncrement() % length, l); + // TODO Alternative to AtomicInteger? The getAndIncrement may be a source of contention on high throughput circuits on large multi-core systems. + // LongAdder isn't suited to this as it is not consistent. Perhaps a different data structure that doesn't need indexed adds? + // A threadlocal data storage that only aggregates when fetched would be ideal. Similar to LongAdder except for accumulating lists of data. + } + } + + public int length() { + if (index.get() > list.length()) { + return list.length(); + } else { + return index.get(); + } + } + + } + + /** + * @NotThreadSafe + */ + /* package for testing */ static class PercentileSnapshot { + private final int[] data; + private final int length; + private int mean; + + /* package for testing */ PercentileSnapshot(Bucket[] buckets) { + int lengthFromBuckets = 0; + // we need to calculate it dynamically as it could have been changed by properties (rare, but possible) + // also this way we capture the actual index size rather than the max so size the int[] to only what we need + for (Bucket bd : buckets) { + lengthFromBuckets += bd.data.length; + } + data = new int[lengthFromBuckets]; + int index = 0; + int sum = 0; + for (Bucket bd : buckets) { + PercentileBucketData pbd = bd.data; + int length = pbd.length(); + for (int i = 0; i < length; i++) { + int v = pbd.list.get(i); + this.data[index++] = v; + sum += v; + } + } + this.length = index; + if (this.length == 0) { + this.mean = 0; + } else { + this.mean = sum / this.length; + } + + Arrays.sort(this.data, 0, length); + } + + /* package for testing */ PercentileSnapshot(int... data) { + this.data = data; + this.length = data.length; + + int sum = 0; + for (int v : data) { + sum += v; + } + this.mean = sum / this.length; + + Arrays.sort(this.data, 0, length); + } + + /* package for testing */ int getMean() { + return mean; + } + + /** + * Provides percentile computation. + */ + public int getPercentile(double percentile) { + if (length == 0) { + return 0; + } + return computePercentile(percentile); + } + + /** + * @see Percentile (Wikipedia) + * @see Percentile + * + * @param percent percentile of data desired + * @return data at the asked-for percentile. Interpolation is used if exactness is not possible + */ + private int computePercentile(double percent) { + // Some just-in-case edge cases + if (length <= 0) { + return 0; + } else if (percent <= 0.0) { + return data[0]; + } else if (percent >= 100.0) { + return data[length - 1]; + } + + // ranking (http://en.wikipedia.org/wiki/Percentile#Alternative_methods) + double rank = (percent / 100.0) * length; + + // linear interpolation between closest ranks + int iLow = (int) Math.floor(rank); + int iHigh = (int) Math.ceil(rank); + assert 0 <= iLow && iLow <= rank && rank <= iHigh && iHigh <= length; + assert (iHigh - iLow) <= 1; + if (iHigh >= length) { + // Another edge case + return data[length - 1]; + } else if (iLow == iHigh) { + return data[iLow]; + } else { + // Interpolate between the two bounding values + return (int) (data[iLow] + (rank - iLow) * (data[iHigh] - data[iLow])); + } + } + + } + + /** + * This is a circular array acting as a FIFO queue. + *

+ * It purposefully does NOT implement Deque or some other Collection interface as it only implements functionality necessary for this RollingNumber use case. + *

+ * Important Thread-Safety Note: This is ONLY thread-safe within the context of RollingNumber and the protection it gives in the getCurrentBucket method. It uses AtomicReference + * objects to ensure anything done outside of getCurrentBucket is thread-safe, and to ensure visibility of changes across threads (ie. volatility) but the addLast and removeFirst + * methods are NOT thread-safe for external access they depend upon the lock.tryLock() protection in getCurrentBucket which ensures only a single thread will access them at at time. + *

+ * benjchristensen => This implementation was chosen based on performance testing I did and documented at: http://benjchristensen.com/2011/10/08/atomiccirculararray/ + */ + /* package for testing */ static class BucketCircularArray implements Iterable { + private final AtomicReference state; + private final int dataLength; // we don't resize, we always stay the same, so remember this + private final int numBuckets; + + /** + * Immutable object that is atomically set every time the state of the BucketCircularArray changes + *

+ * This handles the compound operations + */ + private class ListState { + /* + * this is an AtomicReferenceArray and not a normal Array because we're copying the reference + * between ListState objects and multiple threads could maintain references across these + * compound operations so I want the visibility/concurrency guarantees + */ + private final AtomicReferenceArray data; + private final int size; + private final int tail; + private final int head; + + private ListState(AtomicReferenceArray data, int head, int tail) { + this.head = head; + this.tail = tail; + if (head == 0 && tail == 0) { + size = 0; + } else { + this.size = (tail + dataLength - head) % dataLength; + } + this.data = data; + } + + public Bucket tail() { + if (size == 0) { + return null; + } else { + // we want to get the last item, so size()-1 + return data.get(convert(size - 1)); + } + } + + private Bucket[] getArray() { + /* + * this isn't technically thread-safe since it requires multiple reads on something that can change + * but since we never clear the data directly, only increment/decrement head/tail we would never get a NULL + * just potentially return stale data which we are okay with doing + */ + ArrayList array = new ArrayList(); + for (int i = 0; i < size; i++) { + array.add(data.get(convert(i))); + } + return array.toArray(new Bucket[array.size()]); + } + + private ListState incrementTail() { + /* if incrementing results in growing larger than 'length' which is the max we should be at, then also increment head (equivalent of removeFirst but done atomically) */ + if (size == numBuckets) { + // increment tail and head + return new ListState(data, (head + 1) % dataLength, (tail + 1) % dataLength); + } else { + // increment only tail + return new ListState(data, head, (tail + 1) % dataLength); + } + } + + public ListState clear() { + return new ListState(new AtomicReferenceArray(dataLength), 0, 0); + } + + public ListState addBucket(Bucket b) { + /* + * We could in theory have 2 threads addBucket concurrently and this compound operation would interleave. + *

+ * This should NOT happen since getCurrentBucket is supposed to be executed by a single thread. + *

+ * If it does happen, it's not a huge deal as incrementTail() will be protected by compareAndSet and one of the two addBucket calls will succeed with one of the Buckets. + *

+ * In either case, a single Bucket will be returned as "last" and data loss should not occur and everything keeps in sync for head/tail. + *

+ * Also, it's fine to set it before incrementTail because nothing else should be referencing that index position until incrementTail occurs. + */ + data.set(tail, b); + return incrementTail(); + } + + // The convert() method takes a logical index (as if head was + // always 0) and calculates the index within elementData + private int convert(int index) { + return (index + head) % dataLength; + } + } + + BucketCircularArray(int size) { + AtomicReferenceArray _buckets = new AtomicReferenceArray(size + 1); // + 1 as extra room for the add/remove; + state = new AtomicReference(new ListState(_buckets, 0, 0)); + dataLength = _buckets.length(); + numBuckets = size; + } + + public void clear() { + while (true) { + /* + * it should be very hard to not succeed the first pass thru since this is typically is only called from + * a single thread protected by a tryLock, but there is at least 1 other place (at time of writing this comment) + * where reset can be called from (CircuitBreaker.markSuccess after circuit was tripped) so it can + * in an edge-case conflict. + * + * Instead of trying to determine if someone already successfully called clear() and we should skip + * we will have both calls reset the circuit, even if that means losing data added in between the two + * depending on thread scheduling. + * + * The rare scenario in which that would occur, we'll accept the possible data loss while clearing it + * since the code has stated its desire to clear() anyways. + */ + ListState current = state.get(); + ListState newState = current.clear(); + if (state.compareAndSet(current, newState)) { + return; + } + } + } + + /** + * Returns an iterator on a copy of the internal array so that the iterator won't fail by buckets being added/removed concurrently. + */ + public Iterator iterator() { + return Collections.unmodifiableList(Arrays.asList(getArray())).iterator(); + } + + public void addLast(Bucket o) { + ListState currentState = state.get(); + // create new version of state (what we want it to become) + ListState newState = currentState.addBucket(o); + + /* + * use compareAndSet to set in case multiple threads are attempting (which shouldn't be the case because since addLast will ONLY be called by a single thread at a time due to protection + * provided in getCurrentBucket) + */ + if (state.compareAndSet(currentState, newState)) { + // we succeeded + return; + } else { + // we failed, someone else was adding or removing + // instead of trying again and risking multiple addLast concurrently (which shouldn't be the case) + // we'll just return and let the other thread 'win' and if the timing is off the next call to getCurrentBucket will fix things + return; + } + } + + public int size() { + // the size can also be worked out each time as: + // return (tail + data.length() - head) % data.length(); + return state.get().size; + } + + public Bucket peekLast() { + return state.get().tail(); + } + + private Bucket[] getArray() { + return state.get().getArray(); + } + + } + + /** + * Counters for a given 'bucket' of time. + */ + /* package for testing */ static class Bucket { + final long windowStart; + final PercentileBucketData data; + + Bucket(long startTime, int bucketDataLength) { + this.windowStart = startTime; + this.data = new PercentileBucketData(bucketDataLength); + } + + } + + /* package for testing */ static interface Time { + public long getCurrentTimeInMillis(); + } + + private static class ActualTime implements Time { + + @Override + public long getCurrentTimeInMillis() { + return System.currentTimeMillis(); + } + + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixTimer.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixTimer.java new file mode 100644 index 0000000..3c10e62 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/HystrixTimer.java @@ -0,0 +1,204 @@ +/** + * Copyright 2012 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.util; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Timer used by {@link HystrixCommand} to timeout async executions and {@link HystrixCollapser} to trigger batch executions. + */ +public class HystrixTimer { + + private static final Logger logger = LoggerFactory.getLogger(HystrixTimer.class); + + private static HystrixTimer INSTANCE = new HystrixTimer(); + + private HystrixTimer() { + // private to prevent public instantiation + } + + /** + * Retrieve the global instance. + */ + public static HystrixTimer getInstance() { + return INSTANCE; + } + + /** + * Clears all listeners. + *

+ * NOTE: This will result in race conditions if {@link #addTimerListener(com.netflix.hystrix.util.HystrixTimer.TimerListener)} is being concurrently called. + *

+ */ + public static void reset() { + ScheduledExecutor ex = INSTANCE.executor.getAndSet(null); + if (ex != null && ex.getThreadPool() != null) { + ex.getThreadPool().shutdownNow(); + } + } + + /* package */ AtomicReference executor = new AtomicReference(); + + /** + * Add a {@link TimerListener} that will be executed until it is garbage collected or removed by clearing the returned {@link Reference}. + *

+ * NOTE: It is the responsibility of code that adds a listener via this method to clear this listener when completed. + *

+ *

+ * + *
 {@code
+     * // add a TimerListener 
+     * Reference listener = HystrixTimer.getInstance().addTimerListener(listenerImpl);
+     * 
+     * // sometime later, often in a thread shutdown, request cleanup, servlet filter or something similar the listener must be shutdown via the clear() method
+     * listener.clear();
+     * }
+ *
+ * + * + * @param listener + * TimerListener implementation that will be triggered according to its getIntervalTimeInMilliseconds() method implementation. + * @return reference to the TimerListener that allows cleanup via the clear() method + */ + public Reference addTimerListener(final TimerListener listener) { + startThreadIfNeeded(); + // add the listener + + Runnable r = new Runnable() { + + @Override + public void run() { + try { + listener.tick(); + } catch (Exception e) { + logger.error("Failed while ticking TimerListener", e); + } + } + }; + + ScheduledFuture f = executor.get().getThreadPool().scheduleAtFixedRate(r, listener.getIntervalTimeInMilliseconds(), listener.getIntervalTimeInMilliseconds(), TimeUnit.MILLISECONDS); + return new TimerReference(listener, f); + } + + private static class TimerReference extends SoftReference { + + private final ScheduledFuture f; + + TimerReference(TimerListener referent, ScheduledFuture f) { + super(referent); + this.f = f; + } + + @Override + public void clear() { + super.clear(); + // stop this ScheduledFuture from any further executions + f.cancel(false); + } + + } + + /** + * Since we allow resetting the timer (shutting down the thread) we need to lazily re-start it if it starts being used again. + *

+ * This does the lazy initialization and start of the thread in a thread-safe manner while having little cost the rest of the time. + */ + protected void startThreadIfNeeded() { + // create and start thread if one doesn't exist + while (executor.get() == null || ! executor.get().isInitialized()) { + if (executor.compareAndSet(null, new ScheduledExecutor())) { + // initialize the executor that we 'won' setting + executor.get().initialize(); + } + } + } + + /* package */ static class ScheduledExecutor { + /* package */ volatile ScheduledThreadPoolExecutor executor; + private volatile boolean initialized; + + /** + * We want this only done once when created in compareAndSet so use an initialize method + */ + public void initialize() { + + HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); + int coreSize = propertiesStrategy.getTimerThreadPoolProperties().getCorePoolSize().get(); + + ThreadFactory threadFactory = null; + if (!PlatformSpecific.isAppEngineStandardEnvironment()) { + threadFactory = new ThreadFactory() { + final AtomicInteger counter = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "HystrixTimer-" + counter.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + + }; + } else { + threadFactory = PlatformSpecific.getAppEngineThreadFactory(); + } + + executor = new ScheduledThreadPoolExecutor(coreSize, threadFactory); + initialized = true; + } + + public ScheduledThreadPoolExecutor getThreadPool() { + return executor; + } + + public boolean isInitialized() { + return initialized; + } + } + + public static interface TimerListener { + + /** + * The 'tick' is called each time the interval occurs. + *

+ * This method should NOT block or do any work but instead fire its work asynchronously to perform on another thread otherwise it will prevent the Timer from functioning. + *

+ * This contract is used to keep this implementation single-threaded and simplistic. + *

+ * If you need a ThreadLocal set, you can store the state in the TimerListener, then when tick() is called, set the ThreadLocal to your desired value. + */ + public void tick(); + + /** + * How often this TimerListener should 'tick' defined in milliseconds. + */ + public int getIntervalTimeInMilliseconds(); + } + +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/InternMap.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/InternMap.java new file mode 100644 index 0000000..52bee51 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/InternMap.java @@ -0,0 +1,34 @@ +package com.netflix.hystrix.util; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Utility to have 'intern' - like functionality, which holds single instance of wrapper for a given key + */ +public class InternMap { + private final ConcurrentMap storage = new ConcurrentHashMap(); + private final ValueConstructor valueConstructor; + + public interface ValueConstructor { + V create(K key); + } + + public InternMap(ValueConstructor valueConstructor) { + this.valueConstructor = valueConstructor; + } + + public V interned(K key) { + V existingKey = storage.get(key); + V newKey = null; + if (existingKey == null) { + newKey = valueConstructor.create(key); + existingKey = storage.putIfAbsent(key, newKey); + } + return existingKey != null ? existingKey : newKey; + } + + public int size() { + return storage.size(); + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/LongAdder.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/LongAdder.java new file mode 100644 index 0000000..f7e7b8c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/LongAdder.java @@ -0,0 +1,219 @@ +/** + * Copyright 2012 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.util; + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * From http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ + */ + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicLong; + +/** + * One or more variables that together maintain an initially zero + * {@code long} sum. When updates (method {@link #add}) are contended + * across threads, the set of variables may grow dynamically to reduce + * contention. Method {@link #sum} (or, equivalently, {@link + * #longValue}) returns the current total combined across the + * variables maintaining the sum. + * + *

This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common sum that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

This class extends {@link Number}, but does not define + * methods such as {@code hashCode} and {@code compareTo} because + * instances are expected to be mutated, and so are not useful as + * collection keys. + * + *

jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic + * + * @since 1.8 + * @author Doug Lea + */ +public class LongAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of plus for use in retryUpdate + */ + final long fn(long v, long x) { return v + x; } + + /** + * Creates a new adder with initial sum of zero. + */ + public LongAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Equivalent to {@code add(1)}. + */ + public void increment() { + add(1L); + } + + /** + * Equivalent to {@code add(-1)}. + */ + public void decrement() { + add(-1L); + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot: Invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. + * + * @return the sum + */ + public long sum() { + long sum = base; + Cell[] as = cells; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + sum += a.value; + } + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + internalReset(0L); + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public long sumThenReset() { + long sum = base; + Cell[] as = cells; + base = 0L; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + sum += a.value; + a.value = 0L; + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Long.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public long longValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Returns the {@link #sum} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)sum(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(sum()); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/LongMaxUpdater.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/LongMaxUpdater.java new file mode 100644 index 0000000..74359c2 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/LongMaxUpdater.java @@ -0,0 +1,203 @@ +/** + * Copyright 2012 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.util; + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * From http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ + */ + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; + +/** + * One or more variables that together maintain a running {@code long} + * maximum with initial value {@code Long.MIN_VALUE}. When updates + * (method {@link #update}) are contended across threads, the set of + * variables may grow dynamically to reduce contention. Method {@link + * #max} (or, equivalently, {@link #longValue}) returns the current + * maximum across the variables maintaining updates. + * + *

This class extends {@link Number}, but does not define + * methods such as {@code hashCode} and {@code compareTo} because + * instances are expected to be mutated, and so are not useful as + * collection keys. + * + *

jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic + * + * @since 1.8 + * @author Doug Lea + */ +public class LongMaxUpdater extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of max for use in retryUpdate + */ + final long fn(long v, long x) { return v > x ? v : x; } + + /** + * Creates a new instance with initial maximum of {@code + * Long.MIN_VALUE}. + */ + public LongMaxUpdater() { + base = Long.MIN_VALUE; + } + + /** + * Updates the maximum to be at least the given value. + * + * @param x the value to update + */ + public void update(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || + (b = base) < x && !casBase(b, x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + ((v = a.value) < x && !(uncontended = a.cas(v, x)))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Returns the current maximum. The returned value is + * NOT an atomic snapshot: Invocation in the absence of + * concurrent updates returns an accurate result, but concurrent + * updates that occur while the value is being calculated might + * not be incorporated. + * + * @return the maximum + */ + public long max() { + Cell[] as = cells; + long max = base; + if (as != null) { + int n = as.length; + long v; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null && (v = a.value) > max) + max = v; + } + } + return max; + } + + /** + * Resets variables maintaining updates to {@code Long.MIN_VALUE}. + * This method may be a useful alternative to creating a new + * updater, but is only effective if there are no concurrent + * updates. Because this method is intrinsically racy, it should + * only be used when it is known that no threads are concurrently + * updating. + */ + public void reset() { + internalReset(Long.MIN_VALUE); + } + + /** + * Equivalent in effect to {@link #max} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the maximum + */ + public long maxThenReset() { + Cell[] as = cells; + long max = base; + base = Long.MIN_VALUE; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + long v = a.value; + a.value = Long.MIN_VALUE; + if (v > max) + max = v; + } + } + } + return max; + } + + /** + * Returns the String representation of the {@link #max}. + * @return the String representation of the {@link #max} + */ + public String toString() { + return Long.toString(max()); + } + + /** + * Equivalent to {@link #max}. + * + * @return the maximum + */ + public long longValue() { + return max(); + } + + /** + * Returns the {@link #max} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)max(); + } + + /** + * Returns the {@link #max} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)max(); + } + + /** + * Returns the {@link #max} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)max(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(max()); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/PlatformSpecific.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/PlatformSpecific.java new file mode 100644 index 0000000..6ddf9af --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/PlatformSpecific.java @@ -0,0 +1,91 @@ +/** + * Copyright 2016 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.util; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.ThreadFactory; + +public class PlatformSpecific { + private final Platform platform; + + private enum Platform { + STANDARD, APPENGINE_STANDARD, APPENGINE_FLEXIBLE + } + + private static PlatformSpecific INSTANCE = new PlatformSpecific(); + + private PlatformSpecific() { + platform = determinePlatformReflectively(); + } + + public static boolean isAppEngineStandardEnvironment() { + return INSTANCE.platform == Platform.APPENGINE_STANDARD; + } + + public static boolean isAppEngine() { + return INSTANCE.platform == Platform.APPENGINE_FLEXIBLE || INSTANCE.platform == Platform.APPENGINE_STANDARD; + } + + /* + * This detection mechanism is from Guava - specifically + * http://docs.guava-libraries.googlecode.com/git/javadoc/src-html/com/google/common/util/concurrent/MoreExecutors.html#line.766 + * Added GAE_LONG_APP_ID check to detect only AppEngine Standard Environment + */ + private static Platform determinePlatformReflectively() { + if (System.getProperty("com.google.appengine.runtime.environment") == null) { + return Platform.STANDARD; + } + // GAE_LONG_APP_ID is only set in the GAE Flexible Environment, where we want standard threading + if (System.getenv("GAE_LONG_APP_ID") != null) { + return Platform.APPENGINE_FLEXIBLE; + } + try { + // If the current environment is null, we're not inside AppEngine. + boolean isInsideAppengine = Class.forName("com.google.apphosting.api.ApiProxy") + .getMethod("getCurrentEnvironment") + .invoke(null) != null; + return isInsideAppengine ? Platform.APPENGINE_STANDARD : Platform.STANDARD; + } catch (ClassNotFoundException e) { + // If ApiProxy doesn't exist, we're not on AppEngine at all. + return Platform.STANDARD; + } catch (InvocationTargetException e) { + // If ApiProxy throws an exception, we're not in a proper AppEngine environment. + return Platform.STANDARD; + } catch (IllegalAccessException e) { + // If the method isn't accessible, we're not on a supported version of AppEngine; + return Platform.STANDARD; + } catch (NoSuchMethodException e) { + // If the method doesn't exist, we're not on a supported version of AppEngine; + return Platform.STANDARD; + } + } + + public static ThreadFactory getAppEngineThreadFactory() { + try { + return (ThreadFactory) Class.forName("com.google.appengine.api.ThreadManager") + .getMethod("currentRequestThreadFactory") + .invoke(null); + } catch (IllegalAccessException e) { + throw new RuntimeException("Couldn't invoke ThreadManager.currentRequestThreadFactory", e); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Couldn't invoke ThreadManager.currentRequestThreadFactory", e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Couldn't invoke ThreadManager.currentRequestThreadFactory", e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/Striped64.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/Striped64.java new file mode 100644 index 0000000..657e0c7 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/Striped64.java @@ -0,0 +1,360 @@ +/** + * Copyright 2012 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.util; + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * From http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ + */ + +import java.util.Random; + +/** + * A package-local class holding common representation and mechanics + * for classes supporting dynamic striping on 64bit values. The class + * extends Number so that concrete subclasses must publicly do so. + */ +abstract class Striped64 extends Number { + /* + * This class maintains a lazily-initialized table of atomically + * updated variables, plus an extra "base" field. The table size + * is a power of two. Indexing uses masked per-thread hash codes. + * Nearly all declarations in this class are package-private, + * accessed directly by subclasses. + * + * Table entries are of class Cell; a variant of AtomicLong padded + * to reduce cache contention on most processors. Padding is + * overkill for most Atomics because they are usually irregularly + * scattered in memory and thus don't interfere much with each + * other. But Atomic objects residing in arrays will tend to be + * placed adjacent to each other, and so will most often share + * cache lines (with a huge negative performance impact) without + * this precaution. + * + * In part because Cells are relatively large, we avoid creating + * them until they are needed. When there is no contention, all + * updates are made to the base field. Upon first contention (a + * failed CAS on base update), the table is initialized to size 2. + * The table size is doubled upon further contention until + * reaching the nearest power of two greater than or equal to the + * number of CPUS. Table slots remain empty (null) until they are + * needed. + * + * A single spinlock ("busy") is used for initializing and + * resizing the table, as well as populating slots with new Cells. + * There is no need for a blocking lock: When the lock is not + * available, threads try other slots (or the base). During these + * retries, there is increased contention and reduced locality, + * which is still better than alternatives. + * + * Per-thread hash codes are initialized to random values. + * Contention and/or table collisions are indicated by failed + * CASes when performing an update operation (see method + * retryUpdate). Upon a collision, if the table size is less than + * the capacity, it is doubled in size unless some other thread + * holds the lock. If a hashed slot is empty, and lock is + * available, a new Cell is created. Otherwise, if the slot + * exists, a CAS is tried. Retries proceed by "double hashing", + * using a secondary hash (Marsaglia XorShift) to try to find a + * free slot. + * + * The table size is capped because, when there are more threads + * than CPUs, supposing that each thread were bound to a CPU, + * there would exist a perfect hash function mapping threads to + * slots that eliminates collisions. When we reach capacity, we + * search for this mapping by randomly varying the hash codes of + * colliding threads. Because search is random, and collisions + * only become known via CAS failures, convergence can be slow, + * and because threads are typically not bound to CPUS forever, + * may not occur at all. However, despite these limitations, + * observed contention rates are typically low in these cases. + * + * It is possible for a Cell to become unused when threads that + * once hashed to it terminate, as well as in the case where + * doubling the table causes no thread to hash to it under + * expanded mask. We do not try to detect or remove such cells, + * under the assumption that for long-running instances, observed + * contention levels will recur, so the cells will eventually be + * needed again; and for short-lived ones, it does not matter. + */ + + private static final long serialVersionUID = -3403386352761423917L; + + /** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * The value field is placed between pads, hoping that the JVM doesn't + * reorder them. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + static final class Cell { + volatile long p0, p1, p2, p3, p4, p5, p6; + volatile long value; + volatile long q0, q1, q2, q3, q4, q5, q6; + Cell(long x) { value = x; } + + final boolean cas(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long valueOffset; + static { + try { + UNSAFE = getUnsafe(); + Class ak = Cell.class; + valueOffset = UNSAFE.objectFieldOffset + (ak.getDeclaredField("value")); + } catch (Exception e) { + throw new Error(e); + } + } + + } + + /** + * Holder for the thread-local hash code. The code is initially + * random, but may be set to a different value upon collisions. + */ + static final class HashCode { + static final Random rng = new Random(); + int code; + HashCode() { + int h = rng.nextInt(); // Avoid zero to allow xorShift rehash + code = (h == 0) ? 1 : h; + } + } + + /** + * The corresponding ThreadLocal class + */ + static final class ThreadHashCode extends ThreadLocal { + public HashCode initialValue() { return new HashCode(); } + } + + /** + * Static per-thread hash codes. Shared across all instances to + * reduce ThreadLocal pollution and because adjustments due to + * collisions in one table are likely to be appropriate for + * others. + */ + static final ThreadHashCode threadHashCode = new ThreadHashCode(); + + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells; + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + */ + transient volatile int busy; + + /** + * Package-private default constructor + */ + Striped64() { + } + + /** + * CASes the base field. + */ + final boolean casBase(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); + } + + /** + * CASes the busy field from 0 to 1 to acquire lock. + */ + final boolean casBusy() { + return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); + } + + /** + * Computes the function of current and new value. Subclasses + * should open-code this update function for most uses, but the + * virtualized form is needed within retryUpdate. + * + * @param currentValue the current value (of either base or a cell) + * @param newValue the argument from a user update call + * @return result of the update function + */ + abstract long fn(long currentValue, long newValue); + + /** + * Handles cases of updates involving initialization, resizing, + * creating new Cells, and/or contention. See above for + * explanation. This method suffers the usual non-modularity + * problems of optimistic retry code, relying on rechecked sets of + * reads. + * + * @param x the value + * @param hc the hash code holder + * @param wasUncontended false if CAS failed before call + */ + final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { + int h = hc.code; + boolean collide = false; // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (busy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (busy == 0 && casBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + busy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, fn(v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (busy == 0 && casBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + busy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; + } + else if (busy == 0 && cells == as && casBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + busy = 0; + } + if (init) + break; + } + else if (casBase(v = base, fn(v, x))) + break; // Fall back on using base + } + hc.code = h; // Record index for next time + } + + + /** + * Sets base and all cells to the given value. + */ + final void internalReset(long initialValue) { + Cell[] as = cells; + base = initialValue; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + a.value = initialValue; + } + } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long baseOffset; + private static final long busyOffset; + static { + try { + UNSAFE = getUnsafe(); + Class sk = Striped64.class; + baseOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("base")); + busyOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("busy")); + } catch (Exception e) { + throw new Error(e); + } + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. + * Replace with a simple call to Unsafe.getUnsafe when integrating + * into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static sun.misc.Unsafe getUnsafe() { + try { + return sun.misc.Unsafe.getUnsafe(); + } catch (SecurityException se) { + try { + return java.security.AccessController.doPrivileged + (new java.security + .PrivilegedExceptionAction() { + public sun.misc.Unsafe run() throws Exception { + java.lang.reflect.Field f = sun.misc + .Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + }}); + } catch (java.security.PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", + e.getCause()); + } + } + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/package-info.java b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/package-info.java new file mode 100644 index 0000000..d0ef2f3 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/main/java/com/netflix/hystrix/util/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright 2012 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. + */ +/** + * Common utility classes. + * + * @since 1.0.0 + */ +package com.netflix.hystrix.util; \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/AbstractTestHystrixCommand.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/AbstractTestHystrixCommand.java new file mode 100644 index 0000000..aad41a4 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/AbstractTestHystrixCommand.java @@ -0,0 +1,75 @@ +/** + * 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 com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; + +public interface AbstractTestHystrixCommand extends HystrixObservable, InspectableBuilder { + + enum ExecutionResult { + SUCCESS, FAILURE, ASYNC_FAILURE, HYSTRIX_FAILURE, NOT_WRAPPED_FAILURE, ASYNC_HYSTRIX_FAILURE, RECOVERABLE_ERROR, ASYNC_RECOVERABLE_ERROR, UNRECOVERABLE_ERROR, ASYNC_UNRECOVERABLE_ERROR, BAD_REQUEST, ASYNC_BAD_REQUEST, BAD_REQUEST_NOT_WRAPPED, MULTIPLE_EMITS_THEN_SUCCESS, MULTIPLE_EMITS_THEN_FAILURE, NO_EMITS_THEN_SUCCESS + } + + enum FallbackResult { + UNIMPLEMENTED, SUCCESS, FAILURE, ASYNC_FAILURE, MULTIPLE_EMITS_THEN_SUCCESS, MULTIPLE_EMITS_THEN_FAILURE, NO_EMITS_THEN_SUCCESS + } + + enum CacheEnabled { + YES, NO + } + + HystrixPropertiesStrategy TEST_PROPERTIES_FACTORY = new TestPropertiesFactory(); + + class TestPropertiesFactory extends HystrixPropertiesStrategy { + + @Override + public HystrixCommandProperties getCommandProperties(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) { + if (builder == null) { + builder = HystrixCommandPropertiesTest.getUnitTestPropertiesSetter(); + } + return HystrixCommandPropertiesTest.asMock(builder); + } + + @Override + public HystrixThreadPoolProperties getThreadPoolProperties(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter builder) { + if (builder == null) { + builder = HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder(); + } + return HystrixThreadPoolPropertiesTest.asMock(builder); + } + + @Override + public HystrixCollapserProperties getCollapserProperties(HystrixCollapserKey collapserKey, HystrixCollapserProperties.Setter builder) { + throw new IllegalStateException("not expecting collapser properties"); + } + + @Override + public String getCommandPropertiesCacheKey(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) { + return null; + } + + @Override + public String getThreadPoolPropertiesCacheKey(HystrixThreadPoolKey threadPoolKey, com.netflix.hystrix.HystrixThreadPoolProperties.Setter builder) { + return null; + } + + @Override + public String getCollapserPropertiesCacheKey(HystrixCollapserKey collapserKey, com.netflix.hystrix.HystrixCollapserProperties.Setter builder) { + return null; + } + + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/CommonHystrixCommandTests.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/CommonHystrixCommandTests.java new file mode 100644 index 0000000..4b156c8 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/CommonHystrixCommandTests.java @@ -0,0 +1,805 @@ +/** + * 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 com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.AbstractTestHystrixCommand.CacheEnabled; +import com.netflix.hystrix.AbstractTestHystrixCommand.ExecutionResult; +import com.netflix.hystrix.AbstractTestHystrixCommand.FallbackResult; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import org.junit.Test; +import rx.Observable; +import rx.Scheduler; +import rx.Subscriber; +import rx.functions.Action1; +import rx.functions.Func0; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.*; + +/** + * Place to share code and tests between {@link HystrixCommandTest} and {@link HystrixObservableCommandTest}. + * @param + */ +public abstract class CommonHystrixCommandTests> { + + /** + * Run the command in multiple modes and check that the hook assertions hold in each and that the command succeeds + * @param ctor {@link AbstractTestHystrixCommand} constructor + * @param assertion sequence of assertions to check after the command has completed + */ + abstract void assertHooksOnSuccess(Func0 ctor, Action1 assertion); + + /** + * Run the command in multiple modes and check that the hook assertions hold in each and that the command fails + * @param ctor {@link AbstractTestHystrixCommand} constructor + * @param assertion sequence of assertions to check after the command has completed + */ + abstract void assertHooksOnFailure(Func0 ctor, Action1 assertion); + + /** + * Run the command in multiple modes and check that the hook assertions hold in each and that the command fails + * @param ctor {@link AbstractTestHystrixCommand} constructor + * @param assertion sequence of assertions to check after the command has completed + */ + abstract void assertHooksOnFailure(Func0 ctor, Action1 assertion, boolean failFast); + + /** + * Run the command in multiple modes and check that the hook assertions hold in each and that the command fails as soon as possible + * @param ctor {@link AbstractTestHystrixCommand} constructor + * @param assertion sequence of assertions to check after the command has completed + */ + protected void assertHooksOnFailFast(Func0 ctor, Action1 assertion) { + assertHooksOnFailure(ctor, assertion, true); + } + + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#observe()}, immediately block and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + */ + protected void assertBlockingObserve(C command, Action1 assertion, boolean isSuccess) { + System.out.println("Running command.observe(), immediately blocking and then running assertions..."); + if (isSuccess) { + try { + command.observe().toList().toBlocking().single(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + try { + command.observe().toList().toBlocking().single(); + fail("Expected a command failure!"); + } catch (Exception ex) { + System.out.println("Received expected ex : " + ex); + ex.printStackTrace(); + } + } + + assertion.call(command); + } + + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#observe()}, let the {@link rx.Observable} terminal + * states unblock a {@link java.util.concurrent.CountDownLatch} and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + */ + protected void assertNonBlockingObserve(C command, Action1 assertion, boolean isSuccess) { + System.out.println("Running command.observe(), awaiting terminal state of Observable, then running assertions..."); + final CountDownLatch latch = new CountDownLatch(1); + + Observable o = command.observe(); + + o.subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + latch.countDown(); + } + + @Override + public void onNext(Integer i) { + //do nothing + } + }); + + try { + latch.await(3, TimeUnit.SECONDS); + assertion.call(command); + } catch (Exception e) { + throw new RuntimeException(e); + } + + if (isSuccess) { + try { + o.toList().toBlocking().single(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + try { + o.toList().toBlocking().single(); + fail("Expected a command failure!"); + } catch (Exception ex) { + System.out.println("Received expected ex : " + ex); + ex.printStackTrace(); + } + } + } + + protected void assertSaneHystrixRequestLog(final int numCommands) { + HystrixRequestLog currentRequestLog = HystrixRequestLog.getCurrentRequest(); + + try { + assertEquals(numCommands, currentRequestLog.getAllExecutedCommands().size()); + assertFalse(currentRequestLog.getExecutedCommandsAsString().contains("Executed")); + assertTrue(currentRequestLog.getAllExecutedCommands().iterator().next().getExecutionEvents().size() >= 1); + //Most commands should have 1 execution event, but fallbacks / responses from cache can cause more than 1. They should never have 0 + } catch (Throwable ex) { + System.out.println("Problematic Request log : " + currentRequestLog.getExecutedCommandsAsString() + " , expected : " + numCommands); + throw new RuntimeException(ex); + } + } + + protected void assertCommandExecutionEvents(HystrixInvokableInfo command, HystrixEventType... expectedEventTypes) { + boolean emitExpected = false; + int expectedEmitCount = 0; + + boolean fallbackEmitExpected = false; + int expectedFallbackEmitCount = 0; + + List condensedEmitExpectedEventTypes = new ArrayList(); + + for (HystrixEventType expectedEventType: expectedEventTypes) { + if (expectedEventType.equals(HystrixEventType.EMIT)) { + if (!emitExpected) { + //first EMIT encountered, add it to condensedEmitExpectedEventTypes + condensedEmitExpectedEventTypes.add(HystrixEventType.EMIT); + } + emitExpected = true; + expectedEmitCount++; + } else if (expectedEventType.equals(HystrixEventType.FALLBACK_EMIT)) { + if (!fallbackEmitExpected) { + //first FALLBACK_EMIT encountered, add it to condensedEmitExpectedEventTypes + condensedEmitExpectedEventTypes.add(HystrixEventType.FALLBACK_EMIT); + } + fallbackEmitExpected = true; + expectedFallbackEmitCount++; + } else { + condensedEmitExpectedEventTypes.add(expectedEventType); + } + } + List actualEventTypes = command.getExecutionEvents(); + assertEquals(expectedEmitCount, command.getNumberEmissions()); + assertEquals(expectedFallbackEmitCount, command.getNumberFallbackEmissions()); + assertEquals(condensedEmitExpectedEventTypes, actualEventTypes); + } + + /** + * Threadpool with 1 thread, queue of size 1 + */ + protected static class SingleThreadedPoolWithQueue implements HystrixThreadPool { + + final LinkedBlockingQueue queue; + final ThreadPoolExecutor pool; + private final int rejectionQueueSizeThreshold; + + public SingleThreadedPoolWithQueue(int queueSize) { + this(queueSize, 100); + } + + public SingleThreadedPoolWithQueue(int queueSize, int rejectionQueueSizeThreshold) { + queue = new LinkedBlockingQueue(queueSize); + pool = new ThreadPoolExecutor(1, 1, 1, TimeUnit.MINUTES, queue); + this.rejectionQueueSizeThreshold = rejectionQueueSizeThreshold; + } + + @Override + public ThreadPoolExecutor getExecutor() { + return pool; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + @Override + public Scheduler getScheduler(Func0 shouldInterruptThread) { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread); + } + + @Override + public void markThreadExecution() { + // not used for this test + } + + @Override + public void markThreadCompletion() { + // not used for this test + } + + @Override + public void markThreadRejection() { + // not used for this test + } + + @Override + public boolean isQueueSpaceAvailable() { + return queue.size() < rejectionQueueSizeThreshold; + } + + } + + /** + * Threadpool with 1 thread, queue of size 1 + */ + protected static class SingleThreadedPoolWithNoQueue implements HystrixThreadPool { + + final SynchronousQueue queue; + final ThreadPoolExecutor pool; + + public SingleThreadedPoolWithNoQueue() { + queue = new SynchronousQueue(); + pool = new ThreadPoolExecutor(1, 1, 1, TimeUnit.MINUTES, queue); + } + + @Override + public ThreadPoolExecutor getExecutor() { + return pool; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + @Override + public Scheduler getScheduler(Func0 shouldInterruptThread) { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread); + } + + @Override + public void markThreadExecution() { + // not used for this test + } + + @Override + public void markThreadCompletion() { + // not used for this test + } + + @Override + public void markThreadRejection() { + // not used for this test + } + + @Override + public boolean isQueueSpaceAvailable() { + return true; //let the thread pool reject + } + + } + + + + /** + ********************* SEMAPHORE-ISOLATED Execution Hook Tests *********************************** + */ + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: SUCCESS + */ + @Test + public void testExecutionHookSemaphoreSuccess() { + assertHooksOnSuccess( + new Func0() { + @Override + public C call() { + return getCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, FallbackResult.SUCCESS); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(1, 0, 1)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals("onStart - !onRunStart - onExecutionStart - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: synchronous HystrixBadRequestException + */ + @Test + public void testExecutionHookSemaphoreBadRequestException() { + assertHooksOnFailure( + new Func0() { + @Override + public C call() { + return getCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.BAD_REQUEST, FallbackResult.SUCCESS); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(HystrixBadRequestException.class, hook.getCommandException().getClass()); + assertEquals(HystrixBadRequestException.class, hook.getExecutionException().getClass()); + assertEquals("onStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: synchronous HystrixRuntimeException + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookSemaphoreExceptionNoFallback() { + assertHooksOnFailure( + new Func0() { + @Override + public C call() { + return getCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.FAILURE, FallbackResult.UNIMPLEMENTED); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: synchronous HystrixRuntimeException + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookSemaphoreExceptionSuccessfulFallback() { + assertHooksOnSuccess( + new Func0() { + @Override + public C call() { + return getCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.FAILURE, FallbackResult.SUCCESS); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals("onStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: synchronous HystrixRuntimeException + * Fallback: synchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreExceptionUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0() { + @Override + public C call() { + return getCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.FAILURE, FallbackResult.FAILURE); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookSemaphoreRejectedNoFallback() { + assertHooksOnFailFast( + new Func0() { + @Override + public C call() { + AbstractCommand.TryableSemaphore semaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final C cmd1 = getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 500, FallbackResult.UNIMPLEMENTED, semaphore); + final C cmd2 = getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 500, FallbackResult.UNIMPLEMENTED, semaphore); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.observe(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.observe(); + } + }.start(); + + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + + return getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 500, FallbackResult.UNIMPLEMENTED, semaphore); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - onError - ", hook.executionSequence.toString()); + } + }); + } + + + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookSemaphoreRejectedSuccessfulFallback() { + assertHooksOnSuccess( + new Func0() { + @Override + public C call() { + AbstractCommand.TryableSemaphore semaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final C cmd1 = getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 1500, FallbackResult.SUCCESS, semaphore); + final C cmd2 = getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 1500, FallbackResult.SUCCESS, semaphore); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.observe(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.observe(); + } + }.start(); + + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + + return getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 500, FallbackResult.SUCCESS, semaphore); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + assertEquals("onStart - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: synchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreRejectedUnsuccessfulFallback() { + assertHooksOnFailFast( + new Func0() { + @Override + public C call() { + AbstractCommand.TryableSemaphore semaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final C cmd1 = getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 500, FallbackResult.FAILURE, semaphore); + final C cmd2 = getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 500, FallbackResult.FAILURE, semaphore); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.observe(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.observe(); + } + }.start(); + + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + + return getLatentCommand(ExecutionIsolationStrategy.SEMAPHORE, ExecutionResult.SUCCESS, 500, FallbackResult.FAILURE, semaphore); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookSemaphoreShortCircuitNoFallback() { + assertHooksOnFailFast( + new Func0() { + @Override + public C call() { + return getCircuitOpenCommand(ExecutionIsolationStrategy.SEMAPHORE, FallbackResult.UNIMPLEMENTED); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookSemaphoreShortCircuitSuccessfulFallback() { + assertHooksOnSuccess( + new Func0() { + @Override + public C call() { + return getCircuitOpenCommand(ExecutionIsolationStrategy.SEMAPHORE, FallbackResult.SUCCESS); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + assertEquals("onStart - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: synchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreShortCircuitUnsuccessfulFallback() { + assertHooksOnFailFast( + new Func0() { + @Override + public C call() { + return getCircuitOpenCommand(ExecutionIsolationStrategy.SEMAPHORE, FallbackResult.FAILURE); + } + }, + new Action1() { + @Override + public void call(C command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + ********************* END SEMAPHORE-ISOLATED Execution Hook Tests *********************************** + */ + + /** + * Abstract methods defining a way to instantiate each of the described commands. + * {@link HystrixCommandTest} and {@link HystrixObservableCommandTest} should each provide concrete impls for + * {@link HystrixCommand}s and {@link} HystrixObservableCommand}s, respectively. + */ + + C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult) { + return getCommand(isolationStrategy, executionResult, FallbackResult.UNIMPLEMENTED); + } + + C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency) { + return getCommand(isolationStrategy, executionResult, executionLatency, FallbackResult.UNIMPLEMENTED); + } + + C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, FallbackResult fallbackResult) { + return getCommand(isolationStrategy, executionResult, 0, fallbackResult); + } + + C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult) { + return getCommand(isolationStrategy, executionResult, executionLatency, fallbackResult, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, (executionLatency * 2) + 200, CacheEnabled.NO, "foo", 10, 10); + } + + C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int timeout) { + return getCommand(isolationStrategy, executionResult, executionLatency, fallbackResult, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, timeout, CacheEnabled.NO, "foo", 10, 10); + } + + C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, int executionSemaphoreCount, int fallbackSemaphoreCount) { + return getCommand(isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphoreCount, fallbackSemaphoreCount, false); + } + + C getCommand(HystrixCommandKey key, ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, int executionSemaphoreCount, int fallbackSemaphoreCount) { + AbstractCommand.TryableSemaphoreActual executionSemaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(executionSemaphoreCount)); + AbstractCommand.TryableSemaphoreActual fallbackSemaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(fallbackSemaphoreCount)); + + return getCommand(key, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, false); + } + + C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, int executionSemaphoreCount, int fallbackSemaphoreCount, boolean circuitBreakerDisabled) { + AbstractCommand.TryableSemaphoreActual executionSemaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(executionSemaphoreCount)); + AbstractCommand.TryableSemaphoreActual fallbackSemaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(fallbackSemaphoreCount)); + return getCommand(isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + + C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, int executionSemaphoreCount, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + AbstractCommand.TryableSemaphoreActual executionSemaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(executionSemaphoreCount)); + return getCommand(isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + + abstract C getCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, AbstractCommand.TryableSemaphore executionSemaphore, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled); + + abstract C getCommand(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, AbstractCommand.TryableSemaphore executionSemaphore, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled); + + C getLatentCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int timeout) { + return getCommand(isolationStrategy, executionResult, executionLatency, fallbackResult, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, timeout, CacheEnabled.NO, "foo", 10, 10); + } + + C getLatentCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout) { + return getCommand(isolationStrategy, executionResult, executionLatency, fallbackResult, 0, circuitBreaker, threadPool, timeout, CacheEnabled.NO, "foo", 10, 10); + } + + C getLatentCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, AbstractCommand.TryableSemaphore executionSemaphore) { + AbstractCommand.TryableSemaphoreActual fallbackSemaphore = new AbstractCommand.TryableSemaphoreActual(HystrixProperty.Factory.asProperty(10)); + return getCommand(isolationStrategy, executionResult, executionLatency, fallbackResult, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, (executionLatency * 2) + 200, CacheEnabled.NO, "foo", executionSemaphore, fallbackSemaphore, false); + } + + C getCircuitOpenCommand(ExecutionIsolationStrategy isolationStrategy, FallbackResult fallbackResult) { + HystrixCircuitBreakerTest.TestCircuitBreaker openCircuit = new HystrixCircuitBreakerTest.TestCircuitBreaker().setForceShortCircuit(true); + return getCommand(isolationStrategy, ExecutionResult.SUCCESS, 0, fallbackResult, 0, openCircuit, null, 500, CacheEnabled.NO, "foo", 10, 10, false); + } + + C getSharedCircuitBreakerCommand(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, FallbackResult fallbackResult, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker) { + return getCommand(commandKey, isolationStrategy, ExecutionResult.FAILURE, 0, fallbackResult, 0, circuitBreaker, null, 500, CacheEnabled.NO, "foo", 10, 10); + } + + C getCircuitBreakerDisabledCommand(ExecutionIsolationStrategy isolationStrategy, ExecutionResult executionResult) { + return getCommand(isolationStrategy, executionResult, 0, FallbackResult.UNIMPLEMENTED, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, 500, CacheEnabled.NO, "foo", 10, 10, true); + } + + C getRecoverableErrorCommand(ExecutionIsolationStrategy isolationStrategy, FallbackResult fallbackResult) { + return getCommand(isolationStrategy, ExecutionResult.RECOVERABLE_ERROR, 0, fallbackResult); + } + + C getUnrecoverableErrorCommand(ExecutionIsolationStrategy isolationStrategy, FallbackResult fallbackResult) { + return getCommand(isolationStrategy, ExecutionResult.UNRECOVERABLE_ERROR, 0, fallbackResult); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java new file mode 100644 index 0000000..bdfcd55 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java @@ -0,0 +1,834 @@ +/** + * 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Random; + +import com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +import com.netflix.hystrix.HystrixCircuitBreaker.HystrixCircuitBreakerImpl; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import rx.Observable; +import rx.Subscription; + +/** + * These tests each use a different command key to ensure that running them in parallel doesn't allow the state + * built up during a test to cause others to fail + */ +public class HystrixCircuitBreakerTest { + + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + @Before + public void init() { + for (HystrixCommandMetrics metricsInstance: HystrixCommandMetrics.getInstances()) { + metricsInstance.resetStream(); + } + + HystrixCommandMetrics.reset(); + HystrixCircuitBreaker.Factory.reset(); + Hystrix.reset(); + } + + /** + * A simple circuit breaker intended for unit testing of the {@link HystrixCommand} object, NOT production use. + *

+ * This uses simple logic to 'trip' the circuit after 3 subsequent failures and doesn't recover. + */ + public static class TestCircuitBreaker implements HystrixCircuitBreaker { + + final HystrixCommandMetrics metrics; + private boolean forceShortCircuit = false; + + public TestCircuitBreaker() { + this.metrics = getMetrics(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter()); + forceShortCircuit = false; + } + + public TestCircuitBreaker(HystrixCommandKey commandKey) { + this.metrics = getMetrics(commandKey, HystrixCommandPropertiesTest.getUnitTestPropertiesSetter()); + forceShortCircuit = false; + } + + public TestCircuitBreaker setForceShortCircuit(boolean value) { + this.forceShortCircuit = value; + return this; + } + + @Override + public boolean isOpen() { + System.out.println("metrics : " + metrics.getCommandKey().name() + " : " + metrics.getHealthCounts()); + if (forceShortCircuit) { + return true; + } else { + return metrics.getHealthCounts().getErrorCount() >= 3; + } + } + + @Override + public void markSuccess() { + // we don't need to do anything since we're going to permanently trip the circuit + } + + @Override + public void markNonSuccess() { + + } + + @Override + public boolean attemptExecution() { + return !isOpen(); + } + + @Override + public boolean allowRequest() { + return !isOpen(); + } + + } + + + /** + * Test that if all 'marks' are successes during the test window that it does NOT trip the circuit. + * Test that if all 'marks' are failures during the test window that it trips the circuit. + */ + @Test + public void testTripCircuit() { + String key = "cmd-A"; + try { + HystrixCommand cmd1 = new SuccessCommand(key, 1); + HystrixCommand cmd2 = new SuccessCommand(key, 1); + HystrixCommand cmd3 = new SuccessCommand(key, 1); + HystrixCommand cmd4 = new SuccessCommand(key, 1); + + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + cmd1.execute(); + cmd2.execute(); + cmd3.execute(); + cmd4.execute(); + + // this should still allow requests as everything has been successful + Thread.sleep(100); + //assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + // fail + HystrixCommand cmd5 = new FailureCommand(key, 1); + HystrixCommand cmd6 = new FailureCommand(key, 1); + HystrixCommand cmd7 = new FailureCommand(key, 1); + HystrixCommand cmd8 = new FailureCommand(key, 1); + cmd5.execute(); + cmd6.execute(); + cmd7.execute(); + cmd8.execute(); + + // everything has failed in the test window so we should return false now + Thread.sleep(100); + assertFalse(cb.allowRequest()); + assertTrue(cb.isOpen()); + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * Test that if the % of failures is higher than the threshold that the circuit trips. + */ + @Test + public void testTripCircuitOnFailuresAboveThreshold() { + String key = "cmd-B"; + try { + HystrixCommand cmd1 = new SuccessCommand(key, 60); + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // this should start as allowing requests + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + // success with high latency + cmd1.execute(); + HystrixCommand cmd2 = new SuccessCommand(key, 1); + cmd2.execute(); + HystrixCommand cmd3 = new FailureCommand(key, 1); + cmd3.execute(); + HystrixCommand cmd4 = new SuccessCommand(key, 1); + cmd4.execute(); + HystrixCommand cmd5 = new FailureCommand(key, 1); + cmd5.execute(); + HystrixCommand cmd6 = new SuccessCommand(key, 1); + cmd6.execute(); + HystrixCommand cmd7 = new FailureCommand(key, 1); + cmd7.execute(); + HystrixCommand cmd8 = new FailureCommand(key, 1); + cmd8.execute(); + + // this should trip the circuit as the error percentage is above the threshold + Thread.sleep(100); + assertFalse(cb.allowRequest()); + assertTrue(cb.isOpen()); + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * Test that if the % of failures is higher than the threshold that the circuit trips. + */ + @Test + public void testCircuitDoesNotTripOnFailuresBelowThreshold() { + String key = "cmd-C"; + try { + HystrixCommand cmd1 = new SuccessCommand(key, 60); + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // this should start as allowing requests + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + // success with high latency + cmd1.execute(); + HystrixCommand cmd2 = new SuccessCommand(key, 1); + cmd2.execute(); + HystrixCommand cmd3 = new FailureCommand(key, 1); + cmd3.execute(); + HystrixCommand cmd4 = new SuccessCommand(key, 1); + cmd4.execute(); + HystrixCommand cmd5 = new SuccessCommand(key, 1); + cmd5.execute(); + HystrixCommand cmd6 = new FailureCommand(key, 1); + cmd6.execute(); + HystrixCommand cmd7 = new SuccessCommand(key, 1); + cmd7.execute(); + HystrixCommand cmd8 = new FailureCommand(key, 1); + cmd8.execute(); + + // this should remain closed as the failure threshold is below the percentage limit + Thread.sleep(100); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + System.out.println("Current CircuitBreaker Status : " + cmd1.getMetrics().getHealthCounts()); + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * Test that if all 'marks' are timeouts that it will trip the circuit. + */ + @Test + public void testTripCircuitOnTimeouts() { + String key = "cmd-D"; + try { + HystrixCommand cmd1 = new TimeoutCommand(key); + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // this should start as allowing requests + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + // success with high latency + cmd1.execute(); + HystrixCommand cmd2 = new TimeoutCommand(key); + cmd2.execute(); + HystrixCommand cmd3 = new TimeoutCommand(key); + cmd3.execute(); + HystrixCommand cmd4 = new TimeoutCommand(key); + cmd4.execute(); + + // everything has been a timeout so we should not allow any requests + Thread.sleep(100); + assertFalse(cb.allowRequest()); + assertTrue(cb.isOpen()); + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * Test that if the % of timeouts is higher than the threshold that the circuit trips. + */ + @Test + public void testTripCircuitOnTimeoutsAboveThreshold() { + String key = "cmd-E"; + try { + HystrixCommand cmd1 = new SuccessCommand(key, 60); + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // this should start as allowing requests + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + // success with high latency + cmd1.execute(); + HystrixCommand cmd2 = new SuccessCommand(key, 1); + cmd2.execute(); + HystrixCommand cmd3 = new TimeoutCommand(key); + cmd3.execute(); + HystrixCommand cmd4 = new SuccessCommand(key, 1); + cmd4.execute(); + HystrixCommand cmd5 = new TimeoutCommand(key); + cmd5.execute(); + HystrixCommand cmd6 = new TimeoutCommand(key); + cmd6.execute(); + HystrixCommand cmd7 = new SuccessCommand(key, 1); + cmd7.execute(); + HystrixCommand cmd8 = new TimeoutCommand(key); + cmd8.execute(); + HystrixCommand cmd9 = new TimeoutCommand(key); + cmd9.execute(); + + // this should trip the circuit as the error percentage is above the threshold + Thread.sleep(100); + assertTrue(cb.isOpen()); + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * Test that on an open circuit that a single attempt will be allowed after a window of time to see if issues are resolved. + */ + @Test + public void testSingleTestOnOpenCircuitAfterTimeWindow() { + String key = "cmd-F"; + try { + int sleepWindow = 200; + HystrixCommand cmd1 = new FailureCommand(key, 60); + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // this should start as allowing requests + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + cmd1.execute(); + HystrixCommand cmd2 = new FailureCommand(key, 1); + cmd2.execute(); + HystrixCommand cmd3 = new FailureCommand(key, 1); + cmd3.execute(); + HystrixCommand cmd4 = new FailureCommand(key, 1); + cmd4.execute(); + + // everything has failed in the test window so we should return false now + Thread.sleep(100); + assertFalse(cb.allowRequest()); + assertTrue(cb.isOpen()); + + // wait for sleepWindow to pass + Thread.sleep(sleepWindow + 50); + + // we should now allow 1 request + assertTrue(cb.attemptExecution()); + // but the circuit should still be open + assertTrue(cb.isOpen()); + // and further requests are still blocked + assertFalse(cb.attemptExecution()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * Test that an open circuit is closed after 1 success. This also ensures that the rolling window (containing failures) is cleared after the sleep window + * Otherwise, the next bucket roll would produce another signal to fail unless it is explicitly cleared (via {@link HystrixCommandMetrics#resetStream()}. + */ + @Test + public void testCircuitClosedAfterSuccess() { + String key = "cmd-G"; + try { + int sleepWindow = 100; + HystrixCommand cmd1 = new FailureCommand(key, 1, sleepWindow); + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // this should start as allowing requests + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + cmd1.execute(); + HystrixCommand cmd2 = new FailureCommand(key, 1, sleepWindow); + cmd2.execute(); + HystrixCommand cmd3 = new FailureCommand(key, 1, sleepWindow); + cmd3.execute(); + HystrixCommand cmd4 = new TimeoutCommand(key, sleepWindow); + cmd4.execute(); + + // everything has failed in the test window so we should return false now + Thread.sleep(100); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + System.out.println("CircuitBreaker state 1 : " + cmd1.getMetrics().getHealthCounts()); + assertTrue(cb.isOpen()); + + // wait for sleepWindow to pass + Thread.sleep(sleepWindow + 50); + + // but the circuit should still be open + assertTrue(cb.isOpen()); + + // we should now allow 1 request, and upon success, should cause the circuit to be closed + HystrixCommand cmd5 = new SuccessCommand(key, 60, sleepWindow); + Observable asyncResult = cmd5.observe(); + + // and further requests are still blocked while the singleTest command is in flight + assertFalse(cb.allowRequest()); + + asyncResult.toBlocking().single(); + + // all requests should be open again + + Thread.sleep(100); + System.out.println("CircuitBreaker state 2 : " + cmd1.getMetrics().getHealthCounts()); + assertTrue(cb.allowRequest()); + assertTrue(cb.allowRequest()); + assertTrue(cb.allowRequest()); + // and the circuit should be closed again + assertFalse(cb.isOpen()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * Over a period of several 'windows' a single attempt will be made and fail and then finally succeed and close the circuit. + *

+ * Ensure the circuit is kept open through the entire testing period and that only the single attempt in each window is made. + */ + @Test + public void testMultipleTimeWindowRetriesBeforeClosingCircuit() { + String key = "cmd-H"; + try { + int sleepWindow = 200; + HystrixCommand cmd1 = new FailureCommand(key, 60); + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // this should start as allowing requests + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + cmd1.execute(); + HystrixCommand cmd2 = new FailureCommand(key, 1); + cmd2.execute(); + HystrixCommand cmd3 = new FailureCommand(key, 1); + cmd3.execute(); + HystrixCommand cmd4 = new TimeoutCommand(key); + cmd4.execute(); + + // everything has failed in the test window so we should return false now + System.out.println("!!!! 1: 4 failures, circuit will open on recalc"); + Thread.sleep(100); + + assertTrue(cb.isOpen()); + + // wait for sleepWindow to pass + System.out.println("!!!! 2: Sleep window starting where all commands fail-fast"); + Thread.sleep(sleepWindow + 50); + System.out.println("!!!! 3: Sleep window over, should allow singleTest()"); + + // but the circuit should still be open + assertTrue(cb.isOpen()); + + // we should now allow 1 request, and upon failure, should not affect the circuit breaker, which should remain open + HystrixCommand cmd5 = new FailureCommand(key, 60); + Observable asyncResult5 = cmd5.observe(); + System.out.println("!!!! 4: Kicked off the single-test"); + + // and further requests are still blocked while the singleTest command is in flight + assertFalse(cb.allowRequest()); + System.out.println("!!!! 5: Confirmed that no other requests go out during single-test"); + + asyncResult5.toBlocking().single(); + System.out.println("!!!! 6: SingleTest just completed"); + + // all requests should still be blocked, because the singleTest failed + assertFalse(cb.allowRequest()); + assertFalse(cb.allowRequest()); + assertFalse(cb.allowRequest()); + + // wait for sleepWindow to pass + System.out.println("!!!! 2nd sleep window START"); + Thread.sleep(sleepWindow + 50); + System.out.println("!!!! 2nd sleep window over"); + + // we should now allow 1 request, and upon failure, should not affect the circuit breaker, which should remain open + HystrixCommand cmd6 = new FailureCommand(key, 60); + Observable asyncResult6 = cmd6.observe(); + System.out.println("2nd singleTest just kicked off"); + + //and further requests are still blocked while the singleTest command is in flight + assertFalse(cb.allowRequest()); + System.out.println("confirmed that 2nd singletest only happened once"); + + asyncResult6.toBlocking().single(); + System.out.println("2nd singleTest now over"); + + // all requests should still be blocked, because the singleTest failed + assertFalse(cb.allowRequest()); + assertFalse(cb.allowRequest()); + assertFalse(cb.allowRequest()); + + // wait for sleepWindow to pass + Thread.sleep(sleepWindow + 50); + + // but the circuit should still be open + assertTrue(cb.isOpen()); + + // we should now allow 1 request, and upon success, should cause the circuit to be closed + HystrixCommand cmd7 = new SuccessCommand(key, 60); + Observable asyncResult7 = cmd7.observe(); + + // and further requests are still blocked while the singleTest command is in flight + assertFalse(cb.allowRequest()); + + asyncResult7.toBlocking().single(); + + // all requests should be open again + assertTrue(cb.allowRequest()); + assertTrue(cb.allowRequest()); + assertTrue(cb.allowRequest()); + // and the circuit should be closed again + assertFalse(cb.isOpen()); + + // and the circuit should be closed again + assertFalse(cb.isOpen()); + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * When volume of reporting during a statistical window is lower than a defined threshold the circuit + * will not trip regardless of whatever statistics are calculated. + */ + @Test + public void testLowVolumeDoesNotTripCircuit() { + String key = "cmd-I"; + try { + int sleepWindow = 200; + int lowVolume = 5; + + HystrixCommand cmd1 = new FailureCommand(key, 60, sleepWindow, lowVolume); + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // this should start as allowing requests + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + cmd1.execute(); + HystrixCommand cmd2 = new FailureCommand(key, 1, sleepWindow, lowVolume); + cmd2.execute(); + HystrixCommand cmd3 = new FailureCommand(key, 1, sleepWindow, lowVolume); + cmd3.execute(); + HystrixCommand cmd4 = new FailureCommand(key, 1, sleepWindow, lowVolume); + cmd4.execute(); + + // even though it has all failed we won't trip the circuit because the volume is low + Thread.sleep(100); + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + @Test + public void testUnsubscriptionDoesNotLeaveCircuitStuckHalfOpen() { + String key = "cmd-J"; + try { + int sleepWindow = 200; + + // fail + HystrixCommand cmd1 = new FailureCommand(key, 1, sleepWindow); + HystrixCommand cmd2 = new FailureCommand(key, 1, sleepWindow); + HystrixCommand cmd3 = new FailureCommand(key, 1, sleepWindow); + HystrixCommand cmd4 = new FailureCommand(key, 1, sleepWindow); + cmd1.execute(); + cmd2.execute(); + cmd3.execute(); + cmd4.execute(); + + HystrixCircuitBreaker cb = cmd1.circuitBreaker; + + // everything has failed in the test window so we should return false now + Thread.sleep(100); + assertFalse(cb.allowRequest()); + assertTrue(cb.isOpen()); + + //this should occur after the sleep window, so get executed + //however, it is unsubscribed, so never updates state on the circuit-breaker + HystrixCommand cmd5 = new SuccessCommand(key, 5000, sleepWindow); + + //wait for sleep window to pass + Thread.sleep(sleepWindow + 50); + + Observable o = cmd5.observe(); + Subscription s = o.subscribe(); + s.unsubscribe(); + + //wait for 10 sleep windows, then try a successful command. this should return the circuit to CLOSED + + Thread.sleep(10 * sleepWindow); + HystrixCommand cmd6 = new SuccessCommand(key, 1, sleepWindow); + cmd6.execute(); + + Thread.sleep(100); + assertTrue(cb.allowRequest()); + assertFalse(cb.isOpen()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + } + + /** + * Utility method for creating {@link HystrixCommandMetrics} for unit tests. + */ + private static HystrixCommandMetrics getMetrics(HystrixCommandProperties.Setter properties) { + return HystrixCommandMetrics.getInstance(CommandKeyForUnitTest.KEY_ONE, CommandOwnerForUnitTest.OWNER_ONE, ThreadPoolKeyForUnitTest.THREAD_POOL_ONE, HystrixCommandPropertiesTest.asMock(properties)); + } + + + /** + * Utility method for creating {@link HystrixCommandMetrics} for unit tests. + */ + private static HystrixCommandMetrics getMetrics(HystrixCommandKey commandKey, HystrixCommandProperties.Setter properties) { + return HystrixCommandMetrics.getInstance(commandKey, CommandOwnerForUnitTest.OWNER_ONE, ThreadPoolKeyForUnitTest.THREAD_POOL_ONE, HystrixCommandPropertiesTest.asMock(properties)); + } + + /** + * Utility method for creating {@link HystrixCircuitBreaker} for unit tests. + */ + private static HystrixCircuitBreaker getCircuitBreaker(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixCommandMetrics metrics, HystrixCommandProperties.Setter properties) { + return new HystrixCircuitBreakerImpl(key, commandGroup, HystrixCommandPropertiesTest.asMock(properties), metrics); + } + + private static enum CommandOwnerForUnitTest implements HystrixCommandGroupKey { + OWNER_ONE, OWNER_TWO + } + + private static enum ThreadPoolKeyForUnitTest implements HystrixThreadPoolKey { + THREAD_POOL_ONE, THREAD_POOL_TWO + } + + private static enum CommandKeyForUnitTest implements HystrixCommandKey { + KEY_ONE, KEY_TWO + } + + // ignoring since this never ends ... useful for testing https://github.com/Netflix/Hystrix/issues/236 + @Ignore + @Test + public void testSuccessClosesCircuitWhenBusy() throws InterruptedException { + HystrixPlugins.getInstance().registerCommandExecutionHook(new MyHystrixCommandExecutionHook()); + try { + performLoad(200, 0, 40); + performLoad(250, 100, 40); + performLoad(600, 0, 40); + } finally { + Hystrix.reset(); + } + + } + + void performLoad(int totalNumCalls, int errPerc, int waitMillis) { + + Random rnd = new Random(); + + for (int i = 0; i < totalNumCalls; i++) { + //System.out.println(i); + + try { + boolean err = rnd.nextFloat() * 100 < errPerc; + + TestCommand cmd = new TestCommand(err); + cmd.execute(); + + } catch (Exception e) { + //System.err.println(e.getMessage()); + } + + try { + Thread.sleep(waitMillis); + } catch (InterruptedException e) { + } + } + } + + public class TestCommand extends HystrixCommand { + + boolean error; + + public TestCommand(final boolean error) { + super(HystrixCommandGroupKey.Factory.asKey("group")); + + this.error = error; + } + + @Override + protected String run() throws Exception { + + if (error) { + throw new Exception("forced failure"); + } else { + return "success"; + } + } + + @Override + protected String getFallback() { + if (isFailedExecution()) { + return getFailedExecutionException().getMessage(); + } else { + return "other fail reason"; + } + } + + } + + private class Command extends HystrixCommand { + + private final boolean shouldFail; + private final boolean shouldFailWithBadRequest; + private final long latencyToAdd; + + public Command(String commandKey, boolean shouldFail, boolean shouldFailWithBadRequest, long latencyToAdd, int sleepWindow, int requestVolumeThreshold) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Command")).andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)). + andCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter(). + withExecutionTimeoutInMilliseconds(500). + withCircuitBreakerRequestVolumeThreshold(requestVolumeThreshold). + withCircuitBreakerSleepWindowInMilliseconds(sleepWindow))); + this.shouldFail = shouldFail; + this.shouldFailWithBadRequest = shouldFailWithBadRequest; + this.latencyToAdd = latencyToAdd; + } + + public Command(String commandKey, boolean shouldFail, long latencyToAdd) { + this(commandKey, shouldFail, false, latencyToAdd, 200, 1); + } + + @Override + protected Boolean run() throws Exception { + Thread.sleep(latencyToAdd); + if (shouldFail) { + throw new RuntimeException("induced failure"); + } + if (shouldFailWithBadRequest) { + throw new HystrixBadRequestException("bad request"); + } + return true; + } + + @Override + protected Boolean getFallback() { + return false; + } + } + + private class SuccessCommand extends Command { + + SuccessCommand(String commandKey, long latencyToAdd) { + super(commandKey, false, latencyToAdd); + } + + SuccessCommand(String commandKey, long latencyToAdd, int sleepWindow) { + super(commandKey, false, false, latencyToAdd, sleepWindow, 1); + } + } + + private class FailureCommand extends Command { + + FailureCommand(String commandKey, long latencyToAdd) { + super(commandKey, true, latencyToAdd); + } + + FailureCommand(String commandKey, long latencyToAdd, int sleepWindow) { + super(commandKey, true, false, latencyToAdd, sleepWindow, 1); + } + + FailureCommand(String commandKey, long latencyToAdd, int sleepWindow, int requestVolumeThreshold) { + super(commandKey, true, false, latencyToAdd, sleepWindow, requestVolumeThreshold); + } + } + + private class TimeoutCommand extends Command { + + TimeoutCommand(String commandKey) { + super(commandKey, false, 2000); + } + + TimeoutCommand(String commandKey, int sleepWindow) { + super(commandKey, false, false, 2000, sleepWindow, 1); + } + } + + private class BadRequestCommand extends Command { + BadRequestCommand(String commandKey, long latencyToAdd) { + super(commandKey, false, true, latencyToAdd, 200, 1); + } + + BadRequestCommand(String commandKey, long latencyToAdd, int sleepWindow) { + super(commandKey, false, true, latencyToAdd, sleepWindow, 1); + } + } + + public class MyHystrixCommandExecutionHook extends HystrixCommandExecutionHook { + + @Override + public T onComplete(final HystrixInvokable command, final T response) { + + logHC(command, response); + + return super.onComplete(command, response); + } + + private int counter = 0; + + private void logHC(HystrixInvokable command, T response) { + + if(command instanceof HystrixInvokableInfo) { + HystrixInvokableInfo commandInfo = (HystrixInvokableInfo)command; + HystrixCommandMetrics metrics = commandInfo.getMetrics(); + System.out.println("cb/error-count/%/total: " + + commandInfo.isCircuitBreakerOpen() + " " + + metrics.getHealthCounts().getErrorCount() + " " + + metrics.getHealthCounts().getErrorPercentage() + " " + + metrics.getHealthCounts().getTotalRequests() + " => " + response + " " + commandInfo.getExecutionEvents()); + } + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCollapserTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCollapserTest.java new file mode 100644 index 0000000..78fee72 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCollapserTest.java @@ -0,0 +1,2287 @@ +/** + * 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 com.hystrix.junit.HystrixRequestContextRule; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesCollapserDefault; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; +import com.netflix.hystrix.util.HystrixTimer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; +import com.netflix.hystrix.collapser.CollapserTimer; +import com.netflix.hystrix.collapser.RealCollapserTimer; +import com.netflix.hystrix.collapser.RequestCollapser; +import com.netflix.hystrix.collapser.RequestCollapserFactory; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableHolder; +import com.netflix.hystrix.util.HystrixTimer.TimerListener; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.observers.Subscribers; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +import static org.junit.Assert.*; + +public class HystrixCollapserTest { + @Rule + public HystrixRequestContextRule context = new HystrixRequestContextRule(); + + @Before + public void init() { + HystrixCollapserMetrics.reset(); + HystrixCommandMetrics.reset(); + HystrixPropertiesFactory.reset(); + } + + @Test + public void testTwoRequests() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1); + Future response1 = collapser1.queue(); + HystrixCollapser, String, String> collapser2 = new TestRequestCollapser(timer, 2); + Future response2 = collapser2.queue(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertEquals("1", response1.get()); + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixCollapserMetrics metrics = collapser1.getMetrics(); + assertSame(metrics, collapser2.getMetrics()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertEquals(2, command.getNumberCollapsed()); + } + + @Test + public void testMultipleBatches() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1); + Future response1 = collapser1.queue(); + Future response2 = new TestRequestCollapser(timer, 2).queue(); + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertEquals("1", response1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + + // now request more + Future response3 = new TestRequestCollapser(timer, 3).queue(); + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertEquals("3", response3.get(1000, TimeUnit.MILLISECONDS)); + + // we should have had it execute twice now + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void testMaxRequestsInBatch() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1, 2, 10); + HystrixCollapser, String, String> collapser2 = new TestRequestCollapser(timer, 2, 2, 10); + HystrixCollapser, String, String> collapser3 = new TestRequestCollapser(timer, 3, 2, 10); + System.out.println("*** " + System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Constructed the collapsers"); + Future response1 = collapser1.queue(); + Future response2 = collapser2.queue(); + Future response3 = collapser3.queue(); + System.out.println("*** " +System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " queued the collapsers"); + timer.incrementTime(10); // let time pass that equals the default delay/period + System.out.println("*** " +System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " incremented the virtual timer"); + + assertEquals("1", response1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("3", response3.get(1000, TimeUnit.MILLISECONDS)); + + // we should have had it execute twice because the batch size was 2 + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void testRequestsOverTime() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1); + Future response1 = collapser1.queue(); + timer.incrementTime(5); + Future response2 = new TestRequestCollapser(timer, 2).queue(); + timer.incrementTime(8); + // should execute here + Future response3 = new TestRequestCollapser(timer, 3).queue(); + timer.incrementTime(6); + Future response4 = new TestRequestCollapser(timer, 4).queue(); + timer.incrementTime(8); + // should execute here + Future response5 = new TestRequestCollapser(timer, 5).queue(); + timer.incrementTime(10); + // should execute here + + // wait for all tasks to complete + assertEquals("1", response1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("3", response3.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("4", response4.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("5", response5.get(1000, TimeUnit.MILLISECONDS)); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + class Pair { + final A a; + final B b; + + Pair(A a, B b) { + this.a = a; + this.b = b; + } + } + + class MyCommand extends HystrixCommand>> { + + private final List args; + + public MyCommand(List args) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("BATCH"))); + this.args = args; + } + + @Override + protected List> run() throws Exception { + System.out.println("Executing batch command on : " + Thread.currentThread().getName() + " with args : " + args); + List> results = new ArrayList>(); + for (String arg: args) { + results.add(new Pair(arg, Integer.parseInt(arg))); + } + return results; + } + } + + class MyCollapser extends HystrixCollapser>, Integer, String> { + + private final String arg; + + MyCollapser(String arg, boolean reqCacheEnabled) { + super(HystrixCollapserKey.Factory.asKey("UNITTEST"), + Scope.REQUEST, + new RealCollapserTimer(), + HystrixCollapserProperties.Setter().withRequestCacheEnabled(reqCacheEnabled), + HystrixCollapserMetrics.getInstance(HystrixCollapserKey.Factory.asKey("UNITTEST"), + new HystrixPropertiesCollapserDefault(HystrixCollapserKey.Factory.asKey("UNITTEST"), + HystrixCollapserProperties.Setter()))); + this.arg = arg; + } + + @Override + public String getRequestArgument() { + return arg; + } + + @Override + protected HystrixCommand>> createCommand(Collection> collapsedRequests) { + List args = new ArrayList(collapsedRequests.size()); + for (CollapsedRequest req: collapsedRequests) { + args.add(req.getArgument()); + } + return new MyCommand(args); + } + + @Override + protected void mapResponseToRequests(List> batchResponse, Collection> collapsedRequests) { + for (Pair pair: batchResponse) { + for (CollapsedRequest collapsedReq: collapsedRequests) { + if (collapsedReq.getArgument().equals(pair.a)) { + collapsedReq.setResponse(pair.b); + } + } + } + } + + @Override + protected String getCacheKey() { + return arg; + } + } + + + @Test + public void testDuplicateArgumentsWithRequestCachingOn() throws Exception { + final int NUM = 10; + + List> observables = new ArrayList>(); + for (int i = 0; i < NUM; i++) { + MyCollapser c = new MyCollapser("5", true); + observables.add(c.toObservable()); + } + + List> subscribers = new ArrayList>(); + for (final Observable o: observables) { + final TestSubscriber sub = new TestSubscriber(); + subscribers.add(sub); + + o.subscribe(sub); + } + + Thread.sleep(100); + + //all subscribers should receive the same value + for (TestSubscriber sub: subscribers) { + sub.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + System.out.println("Subscriber received : " + sub.getOnNextEvents()); + sub.assertNoErrors(); + sub.assertValues(5); + } + } + + @Test + public void testDuplicateArgumentsWithRequestCachingOff() throws Exception { + final int NUM = 10; + + List> observables = new ArrayList>(); + for (int i = 0; i < NUM; i++) { + MyCollapser c = new MyCollapser("5", false); + observables.add(c.toObservable()); + } + + List> subscribers = new ArrayList>(); + for (final Observable o: observables) { + final TestSubscriber sub = new TestSubscriber(); + subscribers.add(sub); + + o.subscribe(sub); + } + + Thread.sleep(100); + + AtomicInteger numErrors = new AtomicInteger(0); + AtomicInteger numValues = new AtomicInteger(0); + + // only the first subscriber should receive the value. + // the others should get an error that the batch contains duplicates + for (TestSubscriber sub: subscribers) { + sub.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + if (sub.getOnCompletedEvents().isEmpty()) { + System.out.println(Thread.currentThread().getName() + " Error : " + sub.getOnErrorEvents()); + sub.assertError(IllegalArgumentException.class); + sub.assertNoValues(); + numErrors.getAndIncrement(); + + } else { + System.out.println(Thread.currentThread().getName() + " OnNext : " + sub.getOnNextEvents()); + sub.assertValues(5); + sub.assertCompleted(); + sub.assertNoErrors(); + numValues.getAndIncrement(); + } + } + + assertEquals(1, numValues.get()); + assertEquals(NUM - 1, numErrors.get()); + } + + @Test + public void testUnsubscribeFromSomeDuplicateArgsDoesNotRemoveFromBatch() throws Exception { + final int NUM = 10; + + List> observables = new ArrayList>(); + for (int i = 0; i < NUM; i++) { + MyCollapser c = new MyCollapser("5", true); + observables.add(c.toObservable()); + } + + List> subscribers = new ArrayList>(); + List subscriptions = new ArrayList(); + + for (final Observable o: observables) { + final TestSubscriber sub = new TestSubscriber(); + subscribers.add(sub); + + Subscription s = o.subscribe(sub); + subscriptions.add(s); + } + + + //unsubscribe from all but 1 + for (int i = 0; i < NUM - 1; i++) { + Subscription s = subscriptions.get(i); + s.unsubscribe(); + } + + Thread.sleep(100); + + //all subscribers with an active subscription should receive the same value + for (TestSubscriber sub: subscribers) { + if (!sub.isUnsubscribed()) { + sub.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + System.out.println("Subscriber received : " + sub.getOnNextEvents()); + sub.assertNoErrors(); + sub.assertValues(5); + } else { + System.out.println("Subscriber is unsubscribed"); + } + } + } + + @Test + public void testUnsubscribeOnOneDoesntKillBatch() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1); + Future response1 = collapser1.queue(); + Future response2 = new TestRequestCollapser(timer, 2).queue(); + + // kill the first + response1.cancel(true); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + // the first is cancelled so should return null + try { + response1.get(1000, TimeUnit.MILLISECONDS); + fail("expect CancellationException after cancelling"); + } catch (CancellationException e) { + // expected + } + // we should still get a response on the second + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void testShardedRequests() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestShardedRequestCollapser(timer, "1a"); + Future response1 = collapser1.queue(); + Future response2 = new TestShardedRequestCollapser(timer, "2b").queue(); + Future response3 = new TestShardedRequestCollapser(timer, "3b").queue(); + Future response4 = new TestShardedRequestCollapser(timer, "4a").queue(); + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertEquals("1a", response1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("2b", response2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("3b", response3.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("4a", response4.get(1000, TimeUnit.MILLISECONDS)); + + /* we should get 2 batches since it gets sharded */ + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void testRequestScope() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, "1"); + Future response1 = collapser1.queue(); + Future response2 = new TestRequestCollapser(timer, "2").queue(); + + // simulate a new request + RequestCollapserFactory.resetRequest(); + + Future response3 = new TestRequestCollapser(timer, "3").queue(); + Future response4 = new TestRequestCollapser(timer, "4").queue(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertEquals("1", response1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("3", response3.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("4", response4.get(1000, TimeUnit.MILLISECONDS)); + + // 2 different batches should execute, 1 per request + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void testGlobalScope() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestGloballyScopedRequestCollapser(timer, "1"); + Future response1 = collapser1.queue(); + Future response2 = new TestGloballyScopedRequestCollapser(timer, "2").queue(); + + // simulate a new request + RequestCollapserFactory.resetRequest(); + + Future response3 = new TestGloballyScopedRequestCollapser(timer, "3").queue(); + Future response4 = new TestGloballyScopedRequestCollapser(timer, "4").queue(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertEquals("1", response1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("3", response3.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("4", response4.get(1000, TimeUnit.MILLISECONDS)); + + // despite having cleared the cache in between we should have a single execution because this is on the global not request cache + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(4, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void testErrorHandlingViaFutureException() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapserWithFaultyCreateCommand(timer, "1"); + Future response1 = collapser1.queue(); + Future response2 = new TestRequestCollapserWithFaultyCreateCommand(timer, "2").queue(); + timer.incrementTime(10); // let time pass that equals the default delay/period + + try { + response1.get(1000, TimeUnit.MILLISECONDS); + fail("we should have received an exception"); + } catch (ExecutionException e) { + // what we expect + } + try { + response2.get(1000, TimeUnit.MILLISECONDS); + fail("we should have received an exception"); + } catch (ExecutionException e) { + // what we expect + } + + assertEquals(0, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testErrorHandlingWhenMapToResponseFails() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapserWithFaultyMapToResponse(timer, "1"); + Future response1 = collapser1.queue(); + Future response2 = new TestRequestCollapserWithFaultyMapToResponse(timer, "2").queue(); + timer.incrementTime(10); // let time pass that equals the default delay/period + + try { + response1.get(1000, TimeUnit.MILLISECONDS); + fail("we should have received an exception"); + } catch (ExecutionException e) { + // what we expect + } + try { + response2.get(1000, TimeUnit.MILLISECONDS); + fail("we should have received an exception"); + } catch (ExecutionException e) { + // what we expect + } + + // the batch failed so no executions + // but it still executed the command once + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void testRequestVariableLifecycle1() throws Exception { + HystrixRequestContext reqContext = HystrixRequestContext.initializeContext(); + + // do actual work + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1); + Future response1 = collapser1.queue(); + timer.incrementTime(5); + Future response2 = new TestRequestCollapser(timer, 2).queue(); + timer.incrementTime(8); + // should execute here + Future response3 = new TestRequestCollapser(timer, 3).queue(); + timer.incrementTime(6); + Future response4 = new TestRequestCollapser(timer, 4).queue(); + timer.incrementTime(8); + // should execute here + Future response5 = new TestRequestCollapser(timer, 5).queue(); + timer.incrementTime(10); + // should execute here + + // wait for all tasks to complete + assertEquals("1", response1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("3", response3.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("4", response4.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("5", response5.get(1000, TimeUnit.MILLISECONDS)); + + // each task should have been executed 3 times + for (TestCollapserTimer.ATask t : timer.tasks) { + assertEquals(3, t.task.count.get()); + } + + System.out.println("timer.tasks.size() A: " + timer.tasks.size()); + System.out.println("tasks in test: " + timer.tasks); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + + System.out.println("timer.tasks.size() B: " + timer.tasks.size()); + + HystrixRequestVariableHolder> rv = RequestCollapserFactory.getRequestVariable(new TestRequestCollapser(timer, 1).getCollapserKey().name()); + + reqContext.close(); + + assertNotNull(rv); + // they should have all been removed as part of ThreadContext.remove() + assertEquals(0, timer.tasks.size()); + } + + @Test + public void testRequestVariableLifecycle2() throws Exception { + final HystrixRequestContext reqContext = HystrixRequestContext.initializeContext(); + + final TestCollapserTimer timer = new TestCollapserTimer(); + final ConcurrentLinkedQueue> responses = new ConcurrentLinkedQueue>(); + ConcurrentLinkedQueue threads = new ConcurrentLinkedQueue(); + + // kick off work (simulating a single request with multiple threads) + for (int t = 0; t < 5; t++) { + final int outerLoop = t; + Thread th = new Thread(new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + for (int i = 0; i < 100; i++) { + int uniqueInt = (outerLoop * 100) + i; + responses.add(new TestRequestCollapser(timer, uniqueInt).queue()); + } + } + })); + + threads.add(th); + th.start(); + } + + for (Thread th : threads) { + // wait for each thread to finish + th.join(); + } + + // we expect 5 threads * 100 responses each + assertEquals(500, responses.size()); + + for (Future f : responses) { + // they should not be done yet because the counter hasn't incremented + assertFalse(f.isDone()); + } + + timer.incrementTime(5); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 2); + Future response2 = collapser1.queue(); + timer.incrementTime(8); + // should execute here + Future response3 = new TestRequestCollapser(timer, 3).queue(); + timer.incrementTime(6); + Future response4 = new TestRequestCollapser(timer, 4).queue(); + timer.incrementTime(8); + // should execute here + Future response5 = new TestRequestCollapser(timer, 5).queue(); + timer.incrementTime(10); + // should execute here + + // wait for all tasks to complete + for (Future f : responses) { + f.get(1000, TimeUnit.MILLISECONDS); + } + assertEquals("2", response2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("3", response3.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("4", response4.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("5", response5.get(1000, TimeUnit.MILLISECONDS)); + + // each task should have been executed 3 times + for (TestCollapserTimer.ATask t : timer.tasks) { + assertEquals(3, t.task.count.get()); + } + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(500, cmdIterator.next().getNumberCollapsed()); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + + HystrixRequestVariableHolder> rv = RequestCollapserFactory.getRequestVariable(new TestRequestCollapser(timer, 1).getCollapserKey().name()); + + reqContext.close(); + + assertNotNull(rv); + // they should have all been removed as part of ThreadContext.remove() + assertEquals(0, timer.tasks.size()); + } + + /** + * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future + */ + @Test + public void testRequestCache1() { + final TestCollapserTimer timer = new TestCollapserTimer(); + SuccessfulCacheableCollapsedCommand command1 = new SuccessfulCacheableCollapsedCommand(timer, "A", true); + SuccessfulCacheableCollapsedCommand command2 = new SuccessfulCacheableCollapsedCommand(timer, "A", true); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("A", f2.get(1000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Future f3 = command1.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f3.get(1000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // we should still have executed only one command + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixInvokableInfo[1])[0]; + System.out.println("command.getExecutionEvents(): " + command.getExecutionEvents()); + assertEquals(2, command.getExecutionEvents().size()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + /** + * Test Request scoped caching doesn't prevent different ones from executing + */ + @Test + public void testRequestCache2() { + final TestCollapserTimer timer = new TestCollapserTimer(); + SuccessfulCacheableCollapsedCommand command1 = new SuccessfulCacheableCollapsedCommand(timer, "A", true); + SuccessfulCacheableCollapsedCommand command2 = new SuccessfulCacheableCollapsedCommand(timer, "B", true); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f2.get(1000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Future f3 = command1.queue(); + Future f4 = command2.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f3.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f4.get(1000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // we should still have executed only one command + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixInvokableInfo[1])[0]; + assertEquals(2, command.getExecutionEvents().size()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testRequestCache3() { + final TestCollapserTimer timer = new TestCollapserTimer(); + SuccessfulCacheableCollapsedCommand command1 = new SuccessfulCacheableCollapsedCommand(timer, "A", true); + SuccessfulCacheableCollapsedCommand command2 = new SuccessfulCacheableCollapsedCommand(timer, "B", true); + SuccessfulCacheableCollapsedCommand command3 = new SuccessfulCacheableCollapsedCommand(timer, "B", true); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + Future f3 = command3.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f3.get(1000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Future f4 = command1.queue(); + Future f5 = command2.queue(); + Future f6 = command3.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f4.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f5.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f6.get(1000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // we should still have executed only one command + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixInvokableInfo[1])[0]; + assertEquals(2, command.getExecutionEvents().size()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testNoRequestCache3() { + final TestCollapserTimer timer = new TestCollapserTimer(); + SuccessfulCacheableCollapsedCommand command1 = new SuccessfulCacheableCollapsedCommand(timer, "A", false); + SuccessfulCacheableCollapsedCommand command2 = new SuccessfulCacheableCollapsedCommand(timer, "B", false); + SuccessfulCacheableCollapsedCommand command3 = new SuccessfulCacheableCollapsedCommand(timer, "B", false); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + Future f3 = command3.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f2.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f3.get(1000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Future f4 = command1.queue(); + Future f5 = command2.queue(); + Future f6 = command3.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f4.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f5.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("B", f6.get(1000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // request caching is turned off on this so we expect 2 command executions + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // we expect to see it with SUCCESS and COLLAPSED and both + HystrixInvokableInfo commandA = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixInvokableInfo[2])[0]; + assertEquals(2, commandA.getExecutionEvents().size()); + assertTrue(commandA.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(commandA.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + + // we expect to see it with SUCCESS and COLLAPSED and both + HystrixInvokableInfo commandB = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixInvokableInfo[2])[1]; + assertEquals(2, commandB.getExecutionEvents().size()); + assertTrue(commandB.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(commandB.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); //1 for A, 1 for B. Batch contains only unique arguments (no duplicates) + assertEquals(2, cmdIterator.next().getNumberCollapsed()); //1 for A, 1 for B. Batch contains only unique arguments (no duplicates) + } + + /** + * Test command that uses a null request argument + */ + @Test + public void testRequestCacheWithNullRequestArgument() throws Exception { + ConcurrentLinkedQueue>> commands = new ConcurrentLinkedQueue>>(); + + final TestCollapserTimer timer = new TestCollapserTimer(); + SuccessfulCacheableCollapsedCommand command1 = new SuccessfulCacheableCollapsedCommand(timer, null, true, commands); + SuccessfulCacheableCollapsedCommand command2 = new SuccessfulCacheableCollapsedCommand(timer, null, true, commands); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + assertEquals("NULL", f1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("NULL", f2.get(1000, TimeUnit.MILLISECONDS)); + + // it should have executed 1 command + assertEquals(1, commands.size()); + assertTrue(commands.peek().getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(commands.peek().getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + + Future f3 = command1.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + assertEquals("NULL", f3.get(1000, TimeUnit.MILLISECONDS)); + + // it should still be 1 ... no new executions + assertEquals(1, commands.size()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + + @Test + public void testRequestCacheWithCommandError() { + ConcurrentLinkedQueue>> commands = new ConcurrentLinkedQueue>>(); + + final TestCollapserTimer timer = new TestCollapserTimer(); + SuccessfulCacheableCollapsedCommand command1 = new SuccessfulCacheableCollapsedCommand(timer, "FAILURE", true, commands); + SuccessfulCacheableCollapsedCommand command2 = new SuccessfulCacheableCollapsedCommand(timer, "FAILURE", true, commands); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("A", f2.get(1000, TimeUnit.MILLISECONDS)); + fail("exception should have been thrown"); + } catch (Exception e) { + // expected + } + + // it should have executed 1 command + assertEquals(1, commands.size()); + assertTrue(commands.peek().getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(commands.peek().getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + + Future f3 = command1.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f3.get(1000, TimeUnit.MILLISECONDS)); + fail("exception should have been thrown"); + } catch (Exception e) { + // expected + } + + // it should still be 1 ... no new executions + assertEquals(1, commands.size()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + /** + * Test that a command that times out will still be cached and when retrieved will re-throw the exception. + */ + @Test + public void testRequestCacheWithCommandTimeout() { + ConcurrentLinkedQueue>> commands = new ConcurrentLinkedQueue>>(); + + final TestCollapserTimer timer = new TestCollapserTimer(); + SuccessfulCacheableCollapsedCommand command1 = new SuccessfulCacheableCollapsedCommand(timer, "TIMEOUT", true, commands); + SuccessfulCacheableCollapsedCommand command2 = new SuccessfulCacheableCollapsedCommand(timer, "TIMEOUT", true, commands); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals("A", f2.get(1000, TimeUnit.MILLISECONDS)); + fail("exception should have been thrown"); + } catch (Exception e) { + // expected + } + + // it should have executed 1 command + assertEquals(1, commands.size()); + assertTrue(commands.peek().getExecutionEvents().contains(HystrixEventType.TIMEOUT)); + assertTrue(commands.peek().getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + + Future f3 = command1.queue(); + + // increment past batch time so it executes + timer.incrementTime(15); + + try { + assertEquals("A", f3.get(1000, TimeUnit.MILLISECONDS)); + fail("exception should have been thrown"); + } catch (Exception e) { + // expected + } + + // it should still be 1 ... no new executions + assertEquals(1, commands.size()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + /** + * Test how the collapser behaves when the circuit is short-circuited + */ + @Test + public void testRequestWithCommandShortCircuited() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapserWithShortCircuitedCommand(timer, "1"); + Observable response1 = collapser1.observe(); + Observable response2 = new TestRequestCollapserWithShortCircuitedCommand(timer, "2").observe(); + timer.incrementTime(10); // let time pass that equals the default delay/period + + try { + response1.toBlocking().first(); + fail("we should have received an exception"); + } catch (Exception e) { + e.printStackTrace(); + // what we expect + } + try { + response2.toBlocking().first(); + fail("we should have received an exception"); + } catch (Exception e) { + e.printStackTrace(); + // what we expect + } + + // it will execute once (short-circuited) + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + /** + * Test a Void response type - null being set as response. + * + * @throws Exception + */ + @Test + public void testVoidResponseTypeFireAndForgetCollapsing1() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + TestCollapserWithVoidResponseType collapser1 = new TestCollapserWithVoidResponseType(timer, 1); + Future response1 = collapser1.queue(); + Future response2 = new TestCollapserWithVoidResponseType(timer, 2).queue(); + timer.incrementTime(100); // let time pass that equals the default delay/period + + // normally someone wouldn't wait on these, but we need to make sure they do in fact return + // and not block indefinitely in case someone does call get() + assertEquals(null, response1.get(1000, TimeUnit.MILLISECONDS)); + assertEquals(null, response2.get(1000, TimeUnit.MILLISECONDS)); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + /** + * Test a Void response type - response never being set in mapResponseToRequest + * + * @throws Exception + */ + @Test + public void testVoidResponseTypeFireAndForgetCollapsing2() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + TestCollapserWithVoidResponseTypeAndMissingMapResponseToRequests collapser1 = new TestCollapserWithVoidResponseTypeAndMissingMapResponseToRequests(timer, 1); + Future response1 = collapser1.queue(); + new TestCollapserWithVoidResponseTypeAndMissingMapResponseToRequests(timer, 2).queue(); + timer.incrementTime(100); // let time pass that equals the default delay/period + + // we will fetch one of these just so we wait for completion ... but expect an error + try { + assertEquals(null, response1.get(1000, TimeUnit.MILLISECONDS)); + fail("expected an error as mapResponseToRequests did not set responses"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof IllegalStateException); + assertTrue(e.getCause().getMessage().startsWith("No response set by")); + } + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + /** + * Test a Void response type with execute - response being set in mapResponseToRequest to null + * + * @throws Exception + */ + @Test + public void testVoidResponseTypeFireAndForgetCollapsing3() throws Exception { + CollapserTimer timer = new RealCollapserTimer(); + TestCollapserWithVoidResponseType collapser1 = new TestCollapserWithVoidResponseType(timer, 1); + assertNull(collapser1.execute()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(1, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void testEarlyUnsubscribeExecutedViaToObservable() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1); + Observable response1 = collapser1.toObservable(); + HystrixCollapser, String, String> collapser2 = new TestRequestCollapser(timer, 2); + Observable response2 = collapser2.toObservable(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s1.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertEquals("2", value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixCollapserMetrics metrics = collapser1.getMetrics(); + assertSame(metrics, collapser2.getMetrics()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertEquals(1, command.getNumberCollapsed()); //1 should have been removed from batch + } + + @Test + public void testEarlyUnsubscribeExecutedViaObserve() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1); + Observable response1 = collapser1.observe(); + HystrixCollapser, String, String> collapser2 = new TestRequestCollapser(timer, 2); + Observable response2 = collapser2.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s1.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertEquals("2", value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixCollapserMetrics metrics = collapser1.getMetrics(); + assertSame(metrics, collapser2.getMetrics()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertEquals(1, command.getNumberCollapsed()); //1 should have been removed from batch + } + + @Test + public void testEarlyUnsubscribeFromAllCancelsBatch() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new TestRequestCollapser(timer, 1); + Observable response1 = collapser1.observe(); + HystrixCollapser, String, String> collapser2 = new TestRequestCollapser(timer, 2); + Observable response2 = collapser2.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s1.unsubscribe(); + s2.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertNull(value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testRequestThenCacheHitAndCacheHitUnsubscribed() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response1 = collapser1.observe(); + HystrixCollapser, String, String> collapser2 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response2 = collapser2.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s2.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertEquals("foo", value1.get()); + assertNull(value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS, HystrixEventType.COLLAPSED); + assertEquals(1, command.getNumberCollapsed()); //should only be 1 collapsed - other came from cache, then was cancelled + } + + @Test + public void testRequestThenCacheHitAndOriginalUnsubscribed() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response1 = collapser1.observe(); + HystrixCollapser, String, String> collapser2 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response2 = collapser2.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s1.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertEquals("foo", value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS, HystrixEventType.COLLAPSED); + assertEquals(1, command.getNumberCollapsed()); //should only be 1 collapsed - other came from cache, then was cancelled + } + + @Test + public void testRequestThenTwoCacheHitsOriginalAndOneCacheHitUnsubscribed() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response1 = collapser1.observe(); + HystrixCollapser, String, String> collapser2 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response2 = collapser2.observe(); + HystrixCollapser, String, String> collapser3 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response3 = collapser3.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final CountDownLatch latch3 = new CountDownLatch(1); + + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + final AtomicReference value3 = new AtomicReference(null); + + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + Subscription s3 = response3 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s3 Unsubscribed!"); + latch3.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s3 OnCompleted"); + latch3.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s3 OnError : " + e); + latch3.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s3 OnNext : " + s); + value3.set(s); + } + }); + + s1.unsubscribe(); + s3.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertEquals("foo", value2.get()); + assertNull(value3.get()); + + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS, HystrixEventType.COLLAPSED); + assertEquals(1, command.getNumberCollapsed()); //should only be 1 collapsed - other came from cache, then was cancelled + } + + @Test + public void testRequestThenTwoCacheHitsAllUnsubscribed() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixCollapser, String, String> collapser1 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response1 = collapser1.observe(); + HystrixCollapser, String, String> collapser2 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response2 = collapser2.observe(); + HystrixCollapser, String, String> collapser3 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response3 = collapser3.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final CountDownLatch latch3 = new CountDownLatch(1); + + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + final AtomicReference value3 = new AtomicReference(null); + + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + Subscription s3 = response3 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s3 Unsubscribed!"); + latch3.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s3 OnCompleted"); + latch3.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s3 OnError : " + e); + latch3.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s3 OnNext : " + s); + value3.set(s); + } + }); + + s1.unsubscribe(); + s2.unsubscribe(); + s3.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertNull(value2.get()); + assertNull(value3.get()); + + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + protected void assertCommandExecutionEvents(HystrixInvokableInfo command, HystrixEventType... expectedEventTypes) { + boolean emitExpected = false; + int expectedEmitCount = 0; + + boolean fallbackEmitExpected = false; + int expectedFallbackEmitCount = 0; + + List condensedEmitExpectedEventTypes = new ArrayList(); + + for (HystrixEventType expectedEventType: expectedEventTypes) { + if (expectedEventType.equals(HystrixEventType.EMIT)) { + if (!emitExpected) { + //first EMIT encountered, add it to condensedEmitExpectedEventTypes + condensedEmitExpectedEventTypes.add(HystrixEventType.EMIT); + } + emitExpected = true; + expectedEmitCount++; + } else if (expectedEventType.equals(HystrixEventType.FALLBACK_EMIT)) { + if (!fallbackEmitExpected) { + //first FALLBACK_EMIT encountered, add it to condensedEmitExpectedEventTypes + condensedEmitExpectedEventTypes.add(HystrixEventType.FALLBACK_EMIT); + } + fallbackEmitExpected = true; + expectedFallbackEmitCount++; + } else { + condensedEmitExpectedEventTypes.add(expectedEventType); + } + } + List actualEventTypes = command.getExecutionEvents(); + assertEquals(expectedEmitCount, command.getNumberEmissions()); + assertEquals(expectedFallbackEmitCount, command.getNumberFallbackEmissions()); + assertEquals(condensedEmitExpectedEventTypes, actualEventTypes); + } + + private static class TestRequestCollapser extends HystrixCollapser, String, String> { + + private final String value; + private ConcurrentLinkedQueue>> commandsExecuted; + + public TestRequestCollapser(TestCollapserTimer timer, int value) { + this(timer, String.valueOf(value)); + } + + public TestRequestCollapser(TestCollapserTimer timer, String value) { + this(timer, value, 10000, 10); + } + + public TestRequestCollapser(Scope scope, TestCollapserTimer timer, String value) { + this(scope, timer, value, 10000, 10); + } + + public TestRequestCollapser(TestCollapserTimer timer, String value, ConcurrentLinkedQueue>> executionLog) { + this(timer, value, 10000, 10, executionLog); + } + + public TestRequestCollapser(TestCollapserTimer timer, int value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds) { + this(timer, String.valueOf(value), defaultMaxRequestsInBatch, defaultTimerDelayInMilliseconds); + } + + public TestRequestCollapser(TestCollapserTimer timer, String value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds) { + this(timer, value, defaultMaxRequestsInBatch, defaultTimerDelayInMilliseconds, null); + } + + public TestRequestCollapser(Scope scope, TestCollapserTimer timer, String value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds) { + this(scope, timer, value, defaultMaxRequestsInBatch, defaultTimerDelayInMilliseconds, null); + } + + public TestRequestCollapser(TestCollapserTimer timer, String value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds, ConcurrentLinkedQueue>> executionLog) { + this(Scope.REQUEST, timer, value, defaultMaxRequestsInBatch, defaultTimerDelayInMilliseconds, executionLog); + } + + private static HystrixCollapserMetrics createMetrics() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("COLLAPSER_ONE"); + return HystrixCollapserMetrics.getInstance(key, new HystrixPropertiesCollapserDefault(key, HystrixCollapserProperties.Setter())); + } + + public TestRequestCollapser(Scope scope, TestCollapserTimer timer, String value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds, ConcurrentLinkedQueue>> executionLog) { + // use a CollapserKey based on the CollapserTimer object reference so it's unique for each timer as we don't want caching + // of properties to occur and we're using the default HystrixProperty which typically does caching + super(collapserKeyFromString(timer), scope, timer, HystrixCollapserProperties.Setter().withMaxRequestsInBatch(defaultMaxRequestsInBatch).withTimerDelayInMilliseconds(defaultTimerDelayInMilliseconds), createMetrics()); + this.value = value; + this.commandsExecuted = executionLog; + } + + @Override + public String getRequestArgument() { + return value; + } + + @Override + public HystrixCommand> createCommand(final Collection> requests) { + /* return a mocked command */ + HystrixCommand> command = new TestCollapserCommand(requests); + if (commandsExecuted != null) { + commandsExecuted.add(command); + } + return command; + } + + @Override + public void mapResponseToRequests(List batchResponse, Collection> requests) { + // for simplicity I'll assume it's a 1:1 mapping between lists ... in real implementations they often need to index to maps + // to allow random access as the response size does not match the request size + if (batchResponse.size() != requests.size()) { + throw new RuntimeException("lists don't match in size => " + batchResponse.size() + " : " + requests.size()); + } + int i = 0; + for (CollapsedRequest request : requests) { + request.setResponse(batchResponse.get(i++)); + } + + } + + } + + /** + * Shard on the artificially provided 'type' variable. + */ + private static class TestShardedRequestCollapser extends TestRequestCollapser { + + public TestShardedRequestCollapser(TestCollapserTimer timer, String value) { + super(timer, value); + } + + @Override + protected Collection>> shardRequests(Collection> requests) { + Collection> typeA = new ArrayList>(); + Collection> typeB = new ArrayList>(); + + for (CollapsedRequest request : requests) { + if (request.getArgument().endsWith("a")) { + typeA.add(request); + } else if (request.getArgument().endsWith("b")) { + typeB.add(request); + } + } + + ArrayList>> shards = new ArrayList>>(); + shards.add(typeA); + shards.add(typeB); + return shards; + } + + } + + /** + * Test the global scope + */ + private static class TestGloballyScopedRequestCollapser extends TestRequestCollapser { + + public TestGloballyScopedRequestCollapser(TestCollapserTimer timer, String value) { + super(Scope.GLOBAL, timer, value); + } + + } + + /** + * Throw an exception when creating a command. + */ + private static class TestRequestCollapserWithFaultyCreateCommand extends TestRequestCollapser { + + public TestRequestCollapserWithFaultyCreateCommand(TestCollapserTimer timer, String value) { + super(timer, value); + } + + @Override + public HystrixCommand> createCommand(Collection> requests) { + throw new RuntimeException("some failure"); + } + + } + + /** + * Throw an exception when creating a command. + */ + private static class TestRequestCollapserWithShortCircuitedCommand extends TestRequestCollapser { + + public TestRequestCollapserWithShortCircuitedCommand(TestCollapserTimer timer, String value) { + super(timer, value); + } + + @Override + public HystrixCommand> createCommand(Collection> requests) { + // args don't matter as it's short-circuited + return new ShortCircuitedCommand(); + } + + } + + /** + * Throw an exception when mapToResponse is invoked + */ + private static class TestRequestCollapserWithFaultyMapToResponse extends TestRequestCollapser { + + public TestRequestCollapserWithFaultyMapToResponse(TestCollapserTimer timer, String value) { + super(timer, value); + } + + @Override + public void mapResponseToRequests(List batchResponse, Collection> requests) { + // pretend we blow up with an NPE + throw new NullPointerException("batchResponse was null and we blew up"); + } + + } + + private static class TestCollapserCommand extends TestHystrixCommand> { + + private final Collection> requests; + + TestCollapserCommand(Collection> requests) { + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(500))); + this.requests = requests; + } + + @Override + protected List run() { + System.out.println(">>> TestCollapserCommand run() ... batch size: " + requests.size()); + // simulate a batch request + ArrayList response = new ArrayList(); + for (CollapsedRequest request : requests) { + if (request.getArgument() == null) { + response.add("NULL"); + } else { + if (request.getArgument().equals("FAILURE")) { + throw new NullPointerException("Simulated Error"); + } + if (request.getArgument().equals("TIMEOUT")) { + try { + Thread.sleep(800); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + response.add(request.getArgument()); + } + } + return response; + } + + } + + /** + * A Command implementation that supports caching. + */ + private static class SuccessfulCacheableCollapsedCommand extends TestRequestCollapser { + + private final boolean cacheEnabled; + + public SuccessfulCacheableCollapsedCommand(TestCollapserTimer timer, String value, boolean cacheEnabled) { + super(timer, value); + this.cacheEnabled = cacheEnabled; + } + + public SuccessfulCacheableCollapsedCommand(TestCollapserTimer timer, String value, boolean cacheEnabled, ConcurrentLinkedQueue>> executionLog) { + super(timer, value, executionLog); + this.cacheEnabled = cacheEnabled; + } + + @Override + public String getCacheKey() { + if (cacheEnabled) + return "aCacheKey_" + super.value; + else + return null; + } + } + + private static class ShortCircuitedCommand extends HystrixCommand> { + + protected ShortCircuitedCommand() { + super(HystrixCommand.Setter.withGroupKey( + HystrixCommandGroupKey.Factory.asKey("shortCircuitedCommand")) + .andCommandPropertiesDefaults(HystrixCommandPropertiesTest + .getUnitTestPropertiesSetter() + .withCircuitBreakerForceOpen(true))); + } + + @Override + protected List run() throws Exception { + System.out.println("*** execution (this shouldn't happen)"); + // this won't ever get called as we're forcing short-circuiting + ArrayList values = new ArrayList(); + values.add("hello"); + return values; + } + + } + + private static class FireAndForgetCommand extends HystrixCommand { + + protected FireAndForgetCommand(List values) { + super(HystrixCommand.Setter.withGroupKey( + HystrixCommandGroupKey.Factory.asKey("fireAndForgetCommand")) + .andCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter())); + } + + @Override + protected Void run() throws Exception { + System.out.println("*** FireAndForgetCommand execution: " + Thread.currentThread()); + return null; + } + + } + + /* package */ static class TestCollapserTimer implements CollapserTimer { + + private final ConcurrentLinkedQueue tasks = new ConcurrentLinkedQueue(); + + @Override + public Reference addListener(final TimerListener collapseTask) { + tasks.add(new ATask(new TestTimerListener(collapseTask))); + + /** + * This is a hack that overrides 'clear' of a WeakReference to match the required API + * but then removes the strong-reference we have inside 'tasks'. + *

+ * We do this so our unit tests know if the WeakReference is cleared correctly, and if so then the ATack is removed from 'tasks' + */ + return new SoftReference(collapseTask) { + @Override + public void clear() { + // super.clear(); + for (ATask t : tasks) { + if (t.task.actualListener.equals(collapseTask)) { + tasks.remove(t); + } + } + } + + }; + } + + /** + * Increment time by X. Note that incrementing by multiples of delay or period time will NOT execute multiple times. + *

+ * You must call incrementTime multiple times each increment being larger than 'period' on subsequent calls to cause multiple executions. + *

+ * This is because executing multiple times in a tight-loop would not achieve the correct behavior, such as batching, since it will all execute "now" not after intervals of time. + * + * @param timeInMilliseconds amount of time to increment + */ + public synchronized void incrementTime(int timeInMilliseconds) { + for (ATask t : tasks) { + t.incrementTime(timeInMilliseconds); + } + } + + private static class ATask { + final TestTimerListener task; + final int delay = 10; + + // our relative time that we'll use + volatile int time = 0; + volatile int executionCount = 0; + + private ATask(TestTimerListener task) { + this.task = task; + } + + public synchronized void incrementTime(int timeInMilliseconds) { + time += timeInMilliseconds; + if (task != null) { + if (executionCount == 0) { + System.out.println("ExecutionCount 0 => Time: " + time + " Delay: " + delay); + if (time >= delay) { + // first execution, we're past the delay time + executeTask(); + } + } else { + System.out.println("ExecutionCount 1+ => Time: " + time + " Delay: " + delay); + if (time >= delay) { + // subsequent executions, we're past the interval time + executeTask(); + } + } + } + } + + private synchronized void executeTask() { + System.out.println("Executing task ..."); + task.tick(); + this.time = 0; // we reset time after each execution + this.executionCount++; + System.out.println("executionCount: " + executionCount); + } + } + + } + + private static class TestTimerListener implements TimerListener { + + private final TimerListener actualListener; + private final AtomicInteger count = new AtomicInteger(); + + public TestTimerListener(TimerListener actual) { + this.actualListener = actual; + } + + @Override + public void tick() { + count.incrementAndGet(); + actualListener.tick(); + } + + @Override + public int getIntervalTimeInMilliseconds() { + return 10; + } + + } + + private static HystrixCollapserKey collapserKeyFromString(final Object o) { + return new HystrixCollapserKey() { + + @Override + public String name() { + return String.valueOf(o); + } + + }; + } + + private static class TestCollapserWithVoidResponseType extends HystrixCollapser { + + private final Integer value; + + public TestCollapserWithVoidResponseType(CollapserTimer timer, int value) { + super(collapserKeyFromString(timer), Scope.REQUEST, timer, HystrixCollapserProperties.Setter().withMaxRequestsInBatch(1000).withTimerDelayInMilliseconds(50)); + this.value = value; + } + + @Override + public Integer getRequestArgument() { + return value; + } + + @Override + protected HystrixCommand createCommand(Collection> requests) { + + ArrayList args = new ArrayList(); + for (CollapsedRequest request : requests) { + args.add(request.getArgument()); + } + return new FireAndForgetCommand(args); + } + + @Override + protected void mapResponseToRequests(Void batchResponse, Collection> requests) { + for (CollapsedRequest r : requests) { + r.setResponse(null); + } + } + + } + + private static class TestCollapserWithVoidResponseTypeAndMissingMapResponseToRequests extends HystrixCollapser { + + private final Integer value; + + public TestCollapserWithVoidResponseTypeAndMissingMapResponseToRequests(CollapserTimer timer, int value) { + super(collapserKeyFromString(timer), Scope.REQUEST, timer, HystrixCollapserProperties.Setter().withMaxRequestsInBatch(1000).withTimerDelayInMilliseconds(50)); + this.value = value; + } + + @Override + public Integer getRequestArgument() { + return value; + } + + @Override + protected HystrixCommand createCommand(Collection> requests) { + + ArrayList args = new ArrayList(); + for (CollapsedRequest request : requests) { + args.add(request.getArgument()); + } + return new FireAndForgetCommand(args); + } + + @Override + protected void mapResponseToRequests(Void batchResponse, Collection> requests) { + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandMetricsTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandMetricsTest.java new file mode 100644 index 0000000..a7ba1eb --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandMetricsTest.java @@ -0,0 +1,255 @@ +/** + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import rx.Observable; +import rx.Subscriber; +import rx.observers.SafeSubscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + + +public class HystrixCommandMetricsTest { + + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + @Before + public void init() { + HystrixCommandMetrics.reset(); + Hystrix.reset(); + } + + @Test + public void testGetErrorPercentage() { + String key = "cmd-metrics-A"; + try { + HystrixCommand cmd1 = new SuccessCommand(key, 1); + HystrixCommandMetrics metrics = cmd1.metrics; + cmd1.execute(); + Thread.sleep(100); + assertEquals(0, metrics.getHealthCounts().getErrorPercentage()); + + HystrixCommand cmd2 = new FailureCommand(key, 1); + cmd2.execute(); + Thread.sleep(100); + assertEquals(50, metrics.getHealthCounts().getErrorPercentage()); + + HystrixCommand cmd3 = new SuccessCommand(key, 1); + HystrixCommand cmd4 = new SuccessCommand(key, 1); + cmd3.execute(); + cmd4.execute(); + Thread.sleep(100); + assertEquals(25, metrics.getHealthCounts().getErrorPercentage()); + + HystrixCommand cmd5 = new TimeoutCommand(key); + HystrixCommand cmd6 = new TimeoutCommand(key); + cmd5.execute(); + cmd6.execute(); + Thread.sleep(100); + assertEquals(50, metrics.getHealthCounts().getErrorPercentage()); + + HystrixCommand cmd7 = new SuccessCommand(key, 1); + HystrixCommand cmd8 = new SuccessCommand(key, 1); + HystrixCommand cmd9 = new SuccessCommand(key, 1); + cmd7.execute(); + cmd8.execute(); + cmd9.execute(); + + // latent + HystrixCommand cmd10 = new SuccessCommand(key, 60); + cmd10.execute(); + + // 6 success + 1 latent success + 1 failure + 2 timeout = 10 total + // latent success not considered error + // error percentage = 1 failure + 2 timeout / 10 + Thread.sleep(100); + assertEquals(30, metrics.getHealthCounts().getErrorPercentage()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred: " + e.getMessage()); + } + + } + + @Test + public void testBadRequestsDoNotAffectErrorPercentage() { + String key = "cmd-metrics-B"; + try { + + HystrixCommand cmd1 = new SuccessCommand(key ,1); + HystrixCommandMetrics metrics = cmd1.metrics; + cmd1.execute(); + Thread.sleep(100); + assertEquals(0, metrics.getHealthCounts().getErrorPercentage()); + + HystrixCommand cmd2 = new FailureCommand(key, 1); + cmd2.execute(); + Thread.sleep(100); + assertEquals(50, metrics.getHealthCounts().getErrorPercentage()); + + HystrixCommand cmd3 = new BadRequestCommand(key, 1); + HystrixCommand cmd4 = new BadRequestCommand(key, 1); + try { + cmd3.execute(); + } catch (HystrixBadRequestException ex) { + System.out.println("Caught expected HystrixBadRequestException from cmd3"); + } + try { + cmd4.execute(); + } catch (HystrixBadRequestException ex) { + System.out.println("Caught expected HystrixBadRequestException from cmd4"); + } + Thread.sleep(100); + assertEquals(50, metrics.getHealthCounts().getErrorPercentage()); + + HystrixCommand cmd5 = new FailureCommand(key, 1); + HystrixCommand cmd6 = new FailureCommand(key, 1); + cmd5.execute(); + cmd6.execute(); + Thread.sleep(100); + assertEquals(75, metrics.getHealthCounts().getErrorPercentage()); + } catch (Exception e) { + e.printStackTrace(); + fail("Error occurred : " + e.getMessage()); + } + } + + @Test + public void testCurrentConcurrentExecutionCount() throws InterruptedException { + String key = "cmd-metrics-C"; + + HystrixCommandMetrics metrics = null; + List> cmdResults = new ArrayList>(); + + int NUM_CMDS = 8; + for (int i = 0; i < NUM_CMDS; i++) { + HystrixCommand cmd = new SuccessCommand(key, 900); + if (metrics == null) { + metrics = cmd.metrics; + } + Observable eagerObservable = cmd.observe(); + cmdResults.add(eagerObservable); + } + + try { + Thread.sleep(150); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + System.out.println("ReqLog: " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(NUM_CMDS, metrics.getCurrentConcurrentExecutionCount()); + + final CountDownLatch latch = new CountDownLatch(1); + Observable.merge(cmdResults).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("All commands done"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println("Error duing command execution"); + e.printStackTrace(); + latch.countDown(); + } + + @Override + public void onNext(Boolean aBoolean) { + + } + }); + + latch.await(10000, TimeUnit.MILLISECONDS); + assertEquals(0, metrics.getCurrentConcurrentExecutionCount()); + } + + private class Command extends HystrixCommand { + + private final boolean shouldFail; + private final boolean shouldFailWithBadRequest; + private final long latencyToAdd; + + public Command(String commandKey, boolean shouldFail, boolean shouldFailWithBadRequest, long latencyToAdd) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Command")) + .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)) + .andCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionTimeoutInMilliseconds(1000) + .withCircuitBreakerRequestVolumeThreshold(20))); + this.shouldFail = shouldFail; + this.shouldFailWithBadRequest = shouldFailWithBadRequest; + this.latencyToAdd = latencyToAdd; + } + + @Override + protected Boolean run() throws Exception { + Thread.sleep(latencyToAdd); + if (shouldFail) { + throw new RuntimeException("induced failure"); + } + if (shouldFailWithBadRequest) { + throw new HystrixBadRequestException("bad request"); + } + return true; + } + + @Override + protected Boolean getFallback() { + return false; + } + } + + private class SuccessCommand extends Command { + + SuccessCommand(String commandKey, long latencyToAdd) { + super(commandKey, false, false, latencyToAdd); + } + } + + private class FailureCommand extends Command { + + FailureCommand(String commandKey, long latencyToAdd) { + super(commandKey, true, false, latencyToAdd); + } + } + + private class TimeoutCommand extends Command { + + TimeoutCommand(String commandKey) { + super(commandKey, false, false, 2000); + } + } + + private class BadRequestCommand extends Command { + BadRequestCommand(String commandKey, long latencyToAdd) { + super(commandKey, false, true, latencyToAdd); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandPropertiesTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandPropertiesTest.java new file mode 100644 index 0000000..02e363d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandPropertiesTest.java @@ -0,0 +1,359 @@ +/** + * 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 static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Test; + +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.HystrixCommandProperties.Setter; +import com.netflix.hystrix.strategy.properties.HystrixProperty; + +public class HystrixCommandPropertiesTest { + + /** + * Utility method for creating baseline properties for unit tests. + */ + /* package */static HystrixCommandProperties.Setter getUnitTestPropertiesSetter() { + return new HystrixCommandProperties.Setter() + .withExecutionTimeoutInMilliseconds(1000)// when an execution will be timed out + .withExecutionTimeoutEnabled(true) + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) // we want thread execution by default in tests + .withExecutionIsolationThreadInterruptOnTimeout(true) + .withExecutionIsolationThreadInterruptOnFutureCancel(true) + .withCircuitBreakerForceOpen(false) // we don't want short-circuiting by default + .withCircuitBreakerErrorThresholdPercentage(40) // % of 'marks' that must be failed to trip the circuit + .withMetricsRollingStatisticalWindowInMilliseconds(5000)// milliseconds back that will be tracked + .withMetricsRollingStatisticalWindowBuckets(5) // buckets + .withCircuitBreakerRequestVolumeThreshold(0) // in testing we will not have a threshold unless we're specifically testing that feature + .withCircuitBreakerSleepWindowInMilliseconds(5000000) // milliseconds after tripping circuit before allowing retry (by default set VERY long as we want it to effectively never allow a singleTest for most unit tests) + .withCircuitBreakerEnabled(true) + .withRequestLogEnabled(true) + .withExecutionIsolationSemaphoreMaxConcurrentRequests(20) + .withFallbackIsolationSemaphoreMaxConcurrentRequests(10) + .withFallbackEnabled(true) + .withCircuitBreakerForceClosed(false) + .withMetricsRollingPercentileEnabled(true) + .withRequestCacheEnabled(true) + .withMetricsRollingPercentileWindowInMilliseconds(60000) + .withMetricsRollingPercentileWindowBuckets(12) + .withMetricsRollingPercentileBucketSize(1000) + .withMetricsHealthSnapshotIntervalInMilliseconds(100); + } + + /** + * Return a static representation of the properties with values from the Builder so that UnitTests can create properties that are not affected by the actual implementations which pick up their + * values dynamically. + * + * @param builder command properties builder + * @return HystrixCommandProperties + */ + /* package */static HystrixCommandProperties asMock(final Setter builder) { + return new HystrixCommandProperties(TestKey.TEST) { + + @Override + public HystrixProperty circuitBreakerEnabled() { + return HystrixProperty.Factory.asProperty(builder.getCircuitBreakerEnabled()); + } + + @Override + public HystrixProperty circuitBreakerErrorThresholdPercentage() { + return HystrixProperty.Factory.asProperty(builder.getCircuitBreakerErrorThresholdPercentage()); + } + + @Override + public HystrixProperty circuitBreakerForceClosed() { + return HystrixProperty.Factory.asProperty(builder.getCircuitBreakerForceClosed()); + } + + @Override + public HystrixProperty circuitBreakerForceOpen() { + return HystrixProperty.Factory.asProperty(builder.getCircuitBreakerForceOpen()); + } + + @Override + public HystrixProperty circuitBreakerRequestVolumeThreshold() { + return HystrixProperty.Factory.asProperty(builder.getCircuitBreakerRequestVolumeThreshold()); + } + + @Override + public HystrixProperty circuitBreakerSleepWindowInMilliseconds() { + return HystrixProperty.Factory.asProperty(builder.getCircuitBreakerSleepWindowInMilliseconds()); + } + + @Override + public HystrixProperty executionIsolationSemaphoreMaxConcurrentRequests() { + return HystrixProperty.Factory.asProperty(builder.getExecutionIsolationSemaphoreMaxConcurrentRequests()); + } + + @Override + public HystrixProperty executionIsolationStrategy() { + return HystrixProperty.Factory.asProperty(builder.getExecutionIsolationStrategy()); + } + + @Override + public HystrixProperty executionIsolationThreadInterruptOnTimeout() { + return HystrixProperty.Factory.asProperty(builder.getExecutionIsolationThreadInterruptOnTimeout()); + } + + @Override + public HystrixProperty executionIsolationThreadInterruptOnFutureCancel() { + return HystrixProperty.Factory.asProperty(builder.getExecutionIsolationThreadInterruptOnFutureCancel()); + } + + @Override + public HystrixProperty executionIsolationThreadPoolKeyOverride() { + return HystrixProperty.Factory.nullProperty(); + } + + @Override + public HystrixProperty executionTimeoutInMilliseconds() { + return HystrixProperty.Factory.asProperty(builder.getExecutionTimeoutInMilliseconds()); + } + + @Override + public HystrixProperty executionTimeoutEnabled() { + return HystrixProperty.Factory.asProperty(builder.getExecutionTimeoutEnabled()); + } + + @Override + public HystrixProperty fallbackIsolationSemaphoreMaxConcurrentRequests() { + return HystrixProperty.Factory.asProperty(builder.getFallbackIsolationSemaphoreMaxConcurrentRequests()); + } + + @Override + public HystrixProperty fallbackEnabled() { + return HystrixProperty.Factory.asProperty(builder.getFallbackEnabled()); + } + + @Override + public HystrixProperty metricsHealthSnapshotIntervalInMilliseconds() { + return HystrixProperty.Factory.asProperty(builder.getMetricsHealthSnapshotIntervalInMilliseconds()); + } + + @Override + public HystrixProperty metricsRollingPercentileBucketSize() { + return HystrixProperty.Factory.asProperty(builder.getMetricsRollingPercentileBucketSize()); + } + + @Override + public HystrixProperty metricsRollingPercentileEnabled() { + return HystrixProperty.Factory.asProperty(builder.getMetricsRollingPercentileEnabled()); + } + + @Override + public HystrixProperty metricsRollingPercentileWindow() { + return HystrixProperty.Factory.asProperty(builder.getMetricsRollingPercentileWindowInMilliseconds()); + } + + @Override + public HystrixProperty metricsRollingPercentileWindowBuckets() { + return HystrixProperty.Factory.asProperty(builder.getMetricsRollingPercentileWindowBuckets()); + } + + @Override + public HystrixProperty metricsRollingStatisticalWindowInMilliseconds() { + return HystrixProperty.Factory.asProperty(builder.getMetricsRollingStatisticalWindowInMilliseconds()); + } + + @Override + public HystrixProperty metricsRollingStatisticalWindowBuckets() { + return HystrixProperty.Factory.asProperty(builder.getMetricsRollingStatisticalWindowBuckets()); + } + + @Override + public HystrixProperty requestCacheEnabled() { + return HystrixProperty.Factory.asProperty(builder.getRequestCacheEnabled()); + } + + @Override + public HystrixProperty requestLogEnabled() { + return HystrixProperty.Factory.asProperty(builder.getRequestLogEnabled()); + } + + }; + } + + // NOTE: We use "unitTestPrefix" as a prefix so we can't end up pulling in external properties that change unit test behavior + + public enum TestKey implements HystrixCommandKey { + TEST + } + + private static class TestPropertiesCommand extends HystrixCommandProperties { + + protected TestPropertiesCommand(HystrixCommandKey key, Setter builder, String propertyPrefix) { + super(key, builder, propertyPrefix); + } + + } + + @After + public void cleanup() { + ConfigurationManager.getConfigInstance().clear(); + } + + @Test + public void testBooleanBuilderOverride1() { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, + new HystrixCommandProperties.Setter().withCircuitBreakerForceClosed(true), "unitTestPrefix"); + + // the builder override should take precedence over the default + assertEquals(true, properties.circuitBreakerForceClosed().get()); + } + + @Test + public void testBooleanBuilderOverride2() { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, + new HystrixCommandProperties.Setter().withCircuitBreakerForceClosed(false), "unitTestPrefix"); + + // the builder override should take precedence over the default + assertEquals(false, properties.circuitBreakerForceClosed().get()); + } + + @Test + public void testBooleanCodeDefault() { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, new HystrixCommandProperties.Setter(), "unitTestPrefix"); + assertEquals(HystrixCommandProperties.default_circuitBreakerForceClosed, properties.circuitBreakerForceClosed().get()); + } + + @Test + public void testBooleanGlobalDynamicOverrideOfCodeDefault() throws Exception { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, new HystrixCommandProperties.Setter(), "unitTestPrefix"); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.default.circuitBreaker.forceClosed", true); + + // the global dynamic property should take precedence over the default + assertEquals(true, properties.circuitBreakerForceClosed().get()); + + // cleanup + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.default.circuitBreaker.forceClosed"); + } + + @Test + public void testBooleanInstanceBuilderOverrideOfGlobalDynamicOverride1() throws Exception { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, + new HystrixCommandProperties.Setter().withCircuitBreakerForceClosed(true), "unitTestPrefix"); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.default.circuitBreaker.forceClosed", false); + + // the builder injected should take precedence over the global dynamic property + assertEquals(true, properties.circuitBreakerForceClosed().get()); + + // cleanup + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.default.circuitBreaker.forceClosed"); + } + + @Test + public void testBooleanInstanceBuilderOverrideOfGlobalDynamicOverride2() throws Exception { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, + new HystrixCommandProperties.Setter().withCircuitBreakerForceClosed(false), "unitTestPrefix"); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.default.circuitBreaker.forceClosed", true); + + // the builder injected should take precedence over the global dynamic property + assertEquals(false, properties.circuitBreakerForceClosed().get()); + + // cleanup + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.default.circuitBreaker.forceClosed"); + } + + @Test + public void testBooleanInstanceDynamicOverrideOfEverything() throws Exception { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, + new HystrixCommandProperties.Setter().withCircuitBreakerForceClosed(false), "unitTestPrefix"); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.default.circuitBreaker.forceClosed", false); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.TEST.circuitBreaker.forceClosed", true); + + // the instance specific dynamic property should take precedence over everything + assertEquals(true, properties.circuitBreakerForceClosed().get()); + + // cleanup + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.default.circuitBreaker.forceClosed"); + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.TEST.circuitBreaker.forceClosed"); + } + + @Test + public void testIntegerBuilderOverride() { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, + new HystrixCommandProperties.Setter().withMetricsRollingStatisticalWindowInMilliseconds(5000), "unitTestPrefix"); + + // the builder override should take precedence over the default + assertEquals(5000, properties.metricsRollingStatisticalWindowInMilliseconds().get().intValue()); + } + + @Test + public void testIntegerCodeDefault() { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, new HystrixCommandProperties.Setter(), "unitTestPrefix"); + assertEquals(HystrixCommandProperties.default_metricsRollingStatisticalWindow, properties.metricsRollingStatisticalWindowInMilliseconds().get()); + } + + @Test + public void testIntegerGlobalDynamicOverrideOfCodeDefault() throws Exception { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, new HystrixCommandProperties.Setter(), "unitTestPrefix"); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.default.metrics.rollingStats.timeInMilliseconds", 1234); + + // the global dynamic property should take precedence over the default + assertEquals(1234, properties.metricsRollingStatisticalWindowInMilliseconds().get().intValue()); + + // cleanup + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.default.metrics.rollingStats.timeInMilliseconds"); + } + + @Test + public void testIntegerInstanceBuilderOverrideOfGlobalDynamicOverride() throws Exception { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, + new HystrixCommandProperties.Setter().withMetricsRollingStatisticalWindowInMilliseconds(5000), "unitTestPrefix"); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.default.rollingStats.timeInMilliseconds", 3456); + + // the builder injected should take precedence over the global dynamic property + assertEquals(5000, properties.metricsRollingStatisticalWindowInMilliseconds().get().intValue()); + + // cleanup + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.default.rollingStats.timeInMilliseconds"); + } + + @Test + public void testIntegerInstanceDynamicOverrideOfEverything() throws Exception { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, + new HystrixCommandProperties.Setter().withMetricsRollingStatisticalWindowInMilliseconds(5000), "unitTestPrefix"); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.default.metrics.rollingStats.timeInMilliseconds", 1234); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.TEST.metrics.rollingStats.timeInMilliseconds", 3456); + + // the instance specific dynamic property should take precedence over everything + assertEquals(3456, properties.metricsRollingStatisticalWindowInMilliseconds().get().intValue()); + + // cleanup + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.default.metrics.rollingStats.timeInMilliseconds"); + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.TEST.metrics.rollingStats.timeInMilliseconds"); + } + + @Test + public void testThreadPoolOnlyHasInstanceOverride() throws Exception { + HystrixCommandProperties properties = new TestPropertiesCommand(TestKey.TEST, new HystrixCommandProperties.Setter(), "unitTestPrefix"); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.default.threadPoolKeyOverride", 1234); + // it should be null + assertEquals(null, properties.executionIsolationThreadPoolKeyOverride().get()); + ConfigurationManager.getConfigInstance().setProperty("unitTestPrefix.command.TEST.threadPoolKeyOverride", "testPool"); + // now it should have a value + assertEquals("testPool", properties.executionIsolationThreadPoolKeyOverride().get()); + + // cleanup + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.default.threadPoolKeyOverride"); + ConfigurationManager.getConfigInstance().clearProperty("unitTestPrefix.command.TEST.threadPoolKeyOverride"); + } + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java new file mode 100644 index 0000000..c82990a --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java @@ -0,0 +1,5807 @@ +/** + * 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 com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.AbstractCommand.TryableSemaphore; +import com.netflix.hystrix.AbstractCommand.TryableSemaphoreActual; +import com.netflix.hystrix.HystrixCircuitBreakerTest.TestCircuitBreaker; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import rx.Observable; +import rx.Observer; +import rx.Scheduler; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class HystrixCommandTest extends CommonHystrixCommandTests> { + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + @After + public void cleanup() { + // force properties to be clean as well + ConfigurationManager.getConfigInstance().clear(); + + HystrixCommandKey key = Hystrix.getCurrentThreadExecutingCommand(); + if (key != null) { + System.out.println("WARNING: Hystrix.getCurrentThreadExecutingCommand() should be null but got: " + key + ". Can occur when calling queue() and never retrieving."); + } + } + + /** + * Test a successful command execution. + */ + @Test + public void testExecutionSuccess() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS); + assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, command.execute()); + + assertEquals(null, command.getFailedExecutionException()); + assertNull(command.getExecutionException()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test that a command can not be executed multiple times. + */ + @Test + public void testExecutionMultipleTimes() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS); + assertFalse(command.isExecutionComplete()); + // first should succeed + assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, command.execute()); + assertTrue(command.isExecutionComplete()); + assertTrue(command.isExecutedInThread()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + assertNull(command.getExecutionException()); + + try { + // second should fail + command.execute(); + fail("we should not allow this ... it breaks the state of request logs"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + // we want to get here + } + + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS); + } + + /** + * Test a command execution that throws an HystrixException and didn't implement getFallback. + */ + @Test + public void testExecutionHystrixFailureWithNoFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + try { + command.execute(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertNotNull(e.getFallbackException()); + assertNotNull(e.getImplementingClass()); + } + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution that throws an unknown exception (not HystrixException) and didn't implement getFallback. + */ + @Test + public void testExecutionFailureWithNoFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + try { + command.execute(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertNotNull(e.getFallbackException()); + assertNotNull(e.getImplementingClass()); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution that throws an exception that should not be wrapped. + */ + @Test + public void testNotWrappedExceptionWithNoFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + try { + command.execute(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + fail("we shouldn't get a HystrixRuntimeException"); + } catch (RuntimeException e) { + assertTrue(e instanceof NotWrappedByHystrixTestRuntimeException); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertTrue(command.getExecutionException() instanceof NotWrappedByHystrixTestRuntimeException); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution that throws an exception that should not be wrapped. + */ + @Test + public void testNotWrappedBadRequestWithNoFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST_NOT_WRAPPED, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + try { + command.execute(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + fail("we shouldn't get a HystrixRuntimeException"); + } catch (RuntimeException e) { + assertTrue(e instanceof NotWrappedByHystrixTestRuntimeException); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.getEventCounts().contains(HystrixEventType.BAD_REQUEST)); + assertCommandExecutionEvents(command, HystrixEventType.BAD_REQUEST); + assertNotNull(command.getExecutionException()); + assertTrue(command.getExecutionException() instanceof HystrixBadRequestException); + assertTrue(command.getExecutionException().getCause() instanceof NotWrappedByHystrixTestRuntimeException); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testNotWrappedBadRequestWithFallback() throws Exception { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST_NOT_WRAPPED, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + try { + command.execute(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + fail("we shouldn't get a HystrixRuntimeException"); + } catch (RuntimeException e) { + assertTrue(e instanceof NotWrappedByHystrixTestRuntimeException); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.getEventCounts().contains(HystrixEventType.BAD_REQUEST)); + assertCommandExecutionEvents(command, HystrixEventType.BAD_REQUEST); + assertNotNull(command.getExecutionException()); + assertTrue(command.getExecutionException() instanceof HystrixBadRequestException); + assertTrue(command.getExecutionException().getCause() instanceof NotWrappedByHystrixTestRuntimeException); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution that fails but has a fallback. + */ + @Test + public void testExecutionFailureWithFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.execute()); + assertEquals("Execution Failure for TestHystrixCommand", command.getFailedExecutionException().getMessage()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution that throws exception that should not be wrapped but has a fallback. + */ + @Test + public void testNotWrappedExceptionWithFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.execute()); + assertEquals("Raw exception for TestHystrixCommand", command.getFailedExecutionException().getMessage()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution that fails, has getFallback implemented but that fails as well. + */ + @Test + public void testExecutionFailureWithFallbackFailure() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.FAILURE); + try { + command.execute(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + System.out.println("------------------------------------------------"); + e.printStackTrace(); + System.out.println("------------------------------------------------"); + assertNotNull(e.getFallbackException()); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_FAILURE); + assertNotNull(command.getExecutionException()); + + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a successful command execution (asynchronously). + */ + @Test + public void testQueueSuccess() throws Exception { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS); + Future future = command.queue(); + assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, future.get()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS); + assertNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution (asynchronously) that throws an HystrixException and didn't implement getFallback. + */ + @Test + public void testQueueKnownFailureWithNoFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + try { + command.queue().get(); + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + if (e.getCause() instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e.getCause(); + + assertNotNull(de.getFallbackException()); + assertNotNull(de.getImplementingClass()); + } else { + fail("the cause should be HystrixRuntimeException"); + } + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution (asynchronously) that throws an unknown exception (not HystrixException) and didn't implement getFallback. + */ + @Test + public void testQueueUnknownFailureWithNoFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + try { + command.queue().get(); + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + if (e.getCause() instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e.getCause(); + assertNotNull(de.getFallbackException()); + assertNotNull(de.getImplementingClass()); + } else { + fail("the cause should be HystrixRuntimeException"); + } + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution (asynchronously) that fails but has a fallback. + */ + @Test + public void testQueueFailureWithFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + try { + Future future = command.queue(); + assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, future.get()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution (asynchronously) that fails, has getFallback implemented but that fails as well. + */ + @Test + public void testQueueFailureWithFallbackFailure() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.FAILURE); + try { + command.queue().get(); + fail("we shouldn't get here"); + } catch (Exception e) { + if (e.getCause() instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e.getCause(); + e.printStackTrace(); + assertNotNull(de.getFallbackException()); + } else { + fail("the cause should be HystrixRuntimeException"); + } + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_FAILURE); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a successful command execution. + */ + @Test + public void testObserveSuccess() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS); + assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, command.observe().toBlocking().single()); + assertEquals(null, command.getFailedExecutionException()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS); + assertNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a successful command execution. + */ + @Test + public void testCallbackThreadForThreadIsolation() throws Exception { + + final AtomicReference commandThread = new AtomicReference(); + final AtomicReference subscribeThread = new AtomicReference(); + + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder()) { + + @Override + protected Boolean run() { + commandThread.set(Thread.currentThread()); + return true; + } + }; + + final CountDownLatch latch = new CountDownLatch(1); + + command.toObservable().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + latch.countDown(); + e.printStackTrace(); + } + + @Override + public void onNext(Boolean args) { + subscribeThread.set(Thread.currentThread()); + } + }); + + if (!latch.await(2000, TimeUnit.MILLISECONDS)) { + fail("timed out"); + } + + assertNotNull(commandThread.get()); + assertNotNull(subscribeThread.get()); + + System.out.println("Command Thread: " + commandThread.get()); + System.out.println("Subscribe Thread: " + subscribeThread.get()); + + assertTrue(commandThread.get().getName().startsWith("hystrix-")); + assertTrue(subscribeThread.get().getName().startsWith("hystrix-")); + } + + /** + * Test a successful command execution. + */ + @Test + public void testCallbackThreadForSemaphoreIsolation() throws Exception { + + final AtomicReference commandThread = new AtomicReference(); + final AtomicReference subscribeThread = new AtomicReference(); + + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))) { + + @Override + protected Boolean run() { + commandThread.set(Thread.currentThread()); + return true; + } + }; + + final CountDownLatch latch = new CountDownLatch(1); + + command.toObservable().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + latch.countDown(); + e.printStackTrace(); + } + + @Override + public void onNext(Boolean args) { + subscribeThread.set(Thread.currentThread()); + } + }); + + if (!latch.await(2000, TimeUnit.MILLISECONDS)) { + fail("timed out"); + } + + assertNotNull(commandThread.get()); + assertNotNull(subscribeThread.get()); + + System.out.println("Command Thread: " + commandThread.get()); + System.out.println("Subscribe Thread: " + subscribeThread.get()); + + String mainThreadName = Thread.currentThread().getName(); + + // semaphore should be on the calling thread + assertTrue(commandThread.get().getName().equals(mainThreadName)); + assertTrue(subscribeThread.get().getName().equals(mainThreadName)); + } + + /** + * Tests that the circuit-breaker reports itself as "OPEN" if set as forced-open + */ + @Test + public void testCircuitBreakerReportsOpenIfForcedOpen() { + HystrixCommand cmd = new HystrixCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")).andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withCircuitBreakerForceOpen(true))) { + + @Override + protected Boolean run() throws Exception { + return true; + } + + @Override + protected Boolean getFallback() { + return false; + } + }; + + assertFalse(cmd.execute()); //fallback should fire + System.out.println("RESULT : " + cmd.getExecutionEvents()); + assertTrue(cmd.isCircuitBreakerOpen()); + } + + /** + * Tests that the circuit-breaker reports itself as "CLOSED" if set as forced-closed + */ + @Test + public void testCircuitBreakerReportsClosedIfForcedClosed() { + HystrixCommand cmd = new HystrixCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")).andCommandPropertiesDefaults( + new HystrixCommandProperties.Setter().withCircuitBreakerForceOpen(false).withCircuitBreakerForceClosed(true))) { + + @Override + protected Boolean run() throws Exception { + return true; + } + + @Override + protected Boolean getFallback() { + return false; + } + }; + + assertTrue(cmd.execute()); + System.out.println("RESULT : " + cmd.getExecutionEvents()); + assertFalse(cmd.isCircuitBreakerOpen()); + } + + /** + * Test that the circuit-breaker is shared across HystrixCommand objects with the same CommandKey. + *

+ * This will test HystrixCommand objects with a single circuit-breaker (as if each injected with same CommandKey) + *

+ * Multiple HystrixCommand objects with the same dependency use the same circuit-breaker. + */ + @Test + public void testCircuitBreakerAcrossMultipleCommandsButSameCircuitBreaker() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("SharedCircuitBreaker"); + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(key); + /* fail 3 times and then it should trip the circuit and stop executing */ + // failure 1 + TestHystrixCommand attempt1 = getSharedCircuitBreakerCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker); + System.out.println("COMMAND KEY (from cmd): " + attempt1.commandKey.name()); + attempt1.execute(); + Thread.sleep(100); + assertTrue(attempt1.isResponseFromFallback()); + assertFalse(attempt1.isCircuitBreakerOpen()); + assertFalse(attempt1.isResponseShortCircuited()); + + // failure 2 with a different command, same circuit breaker + TestHystrixCommand attempt2 = getSharedCircuitBreakerCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker); + attempt2.execute(); + Thread.sleep(100); + assertTrue(attempt2.isFailedExecution()); + assertTrue(attempt2.isResponseFromFallback()); + assertFalse(attempt2.isCircuitBreakerOpen()); + assertFalse(attempt2.isResponseShortCircuited()); + + // failure 3 of the Hystrix, 2nd for this particular HystrixCommand + TestHystrixCommand attempt3 = getSharedCircuitBreakerCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker); + attempt3.execute(); + Thread.sleep(100); + assertTrue(attempt3.isFailedExecution()); + assertTrue(attempt3.isResponseFromFallback()); + assertFalse(attempt3.isResponseShortCircuited()); + + // it should now be 'open' and prevent further executions + // after having 3 failures on the Hystrix that these 2 different HystrixCommand objects are for + assertTrue(attempt3.isCircuitBreakerOpen()); + + // attempt 4 + TestHystrixCommand attempt4 = getSharedCircuitBreakerCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker); + attempt4.execute(); + Thread.sleep(100); + assertTrue(attempt4.isResponseFromFallback()); + // this should now be true as the response will be short-circuited + assertTrue(attempt4.isResponseShortCircuited()); + // this should remain open + assertTrue(attempt4.isCircuitBreakerOpen()); + + assertSaneHystrixRequestLog(4); + assertCommandExecutionEvents(attempt1, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(attempt2, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(attempt3, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(attempt4, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_SUCCESS); + } + + /** + * Test that the circuit-breaker being disabled doesn't wreak havoc. + */ + @Test + public void testExecutionSuccessWithCircuitBreakerDisabled() { + TestHystrixCommand command = getCircuitBreakerDisabledCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS); + assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, command.execute()); + + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + // we'll still get metrics ... just not the circuit breaker opening/closing + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS); + } + + /** + * Test a command execution timeout where the command didn't implement getFallback. + */ + @Test + public void testExecutionTimeoutWithNoFallback() { + TestHystrixCommand command = getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50); + try { + command.execute(); + fail("we shouldn't get here"); + } catch (Exception e) { + // e.printStackTrace(); + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be HystrixRuntimeException"); + } + } + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + + assertTrue(command.isResponseTimedOut()); + assertFalse(command.isResponseFromFallback()); + assertFalse(command.isResponseRejected()); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution timeout where the command implemented getFallback. + */ + @Test + public void testExecutionTimeoutWithFallback() { + TestHystrixCommand command = getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50); + assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.execute()); + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertFalse(command.isCircuitBreakerOpen()); + assertFalse(command.isResponseShortCircuited()); + assertTrue(command.isResponseTimedOut()); + assertTrue(command.isResponseFromFallback()); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a command execution timeout where the command implemented getFallback but it fails. + */ + @Test + public void testExecutionTimeoutFallbackFailure() { + TestHystrixCommand command = getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.FAILURE, 50); + try { + command.execute(); + fail("we shouldn't get here"); + } catch (Exception e) { + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertFalse(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be HystrixRuntimeException"); + } + } + + assertNotNull(command.getExecutionException()); + + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test that the command finishing AFTER a timeout (because thread continues in background) does not register a SUCCESS + */ + @Test + public void testCountersOnExecutionTimeout() throws Exception { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50); + command.execute(); + + /* wait long enough for the command to have finished */ + Thread.sleep(200); + + /* response should still be the same as 'testCircuitBreakerOnExecutionTimeout' */ + assertTrue(command.isResponseFromFallback()); + assertFalse(command.isCircuitBreakerOpen()); + assertFalse(command.isResponseShortCircuited()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isResponseTimedOut()); + assertFalse(command.isSuccessfulExecution()); + assertNotNull(command.getExecutionException()); + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a queued command execution timeout where the command didn't implement getFallback. + *

+ * We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the execute() command. + */ + @Test + public void testQueuedExecutionTimeoutWithNoFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50); + try { + command.queue().get(); + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + if (e instanceof ExecutionException && e.getCause() instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e.getCause(); + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be ExecutionException with cause as HystrixRuntimeException"); + } + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isResponseTimedOut()); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a queued command execution timeout where the command implemented getFallback. + *

+ * We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the execute() command. + */ + @Test + public void testQueuedExecutionTimeoutWithFallback() throws Exception { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50); + assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.queue().get()); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a queued command execution timeout where the command implemented getFallback but it fails. + *

+ * We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the execute() command. + */ + @Test + public void testQueuedExecutionTimeoutFallbackFailure() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.FAILURE, 50); + try { + command.queue().get(); + fail("we shouldn't get here"); + } catch (Exception e) { + if (e instanceof ExecutionException && e.getCause() instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e.getCause(); + assertNotNull(de.getFallbackException()); + assertFalse(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be ExecutionException with cause as HystrixRuntimeException"); + } + } + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a queued command execution timeout where the command didn't implement getFallback. + *

+ * We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the execute() command. + */ + @Test + public void testObservedExecutionTimeoutWithNoFallback() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be ExecutionException with cause as HystrixRuntimeException"); + } + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isResponseTimedOut()); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a queued command execution timeout where the command implemented getFallback. + *

+ * We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the execute() command. + */ + @Test + public void testObservedExecutionTimeoutWithFallback() throws Exception { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50); + assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.observe().toBlocking().single()); + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a queued command execution timeout where the command implemented getFallback but it fails. + *

+ * We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the execute() command. + */ + @Test + public void testObservedExecutionTimeoutFallbackFailure() { + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.FAILURE, 50); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (Exception e) { + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertFalse(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be ExecutionException with cause as HystrixRuntimeException"); + } + } + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testShortCircuitFallbackCounter() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + KnownFailureTestCommandWithFallback command1 = new KnownFailureTestCommandWithFallback(circuitBreaker); + command1.execute(); + + KnownFailureTestCommandWithFallback command2 = new KnownFailureTestCommandWithFallback(circuitBreaker); + command2.execute(); + + // will be -1 because it never attempted execution + assertTrue(command1.getExecutionTimeInMilliseconds() == -1); + assertTrue(command1.isResponseShortCircuited()); + assertFalse(command1.isResponseTimedOut()); + assertNotNull(command1.getExecutionException()); + + + assertCommandExecutionEvents(command1, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Test when a command fails to get queued up in the threadpool where the command didn't implement getFallback. + *

+ * We specifically want to protect against developers getting random thread exceptions and instead just correctly receiving HystrixRuntimeException when no fallback exists. + */ + @Test + public void testRejectedThreadWithNoFallback() throws Exception { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Rejection-NoFallback"); + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1); + // fill up the queue + pool.queue.add(new Runnable() { + + @Override + public void run() { + System.out.println("**** queue filler1 ****"); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + }); + + Future f = null; + TestCommandRejection command1 = null; + TestCommandRejection command2 = null; + try { + command1 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + f = command1.queue(); + command2 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + command2.queue(); + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("command.getExecutionTimeInMilliseconds(): " + command2.getExecutionTimeInMilliseconds()); + // will be -1 because it never attempted execution + assertTrue(command2.isResponseRejected()); + assertFalse(command2.isResponseShortCircuited()); + assertFalse(command2.isResponseTimedOut()); + assertNotNull(command2.getExecutionException()); + + if (e instanceof HystrixRuntimeException && e.getCause() instanceof RejectedExecutionException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof RejectedExecutionException); + } else { + fail("the exception should be HystrixRuntimeException with cause as RejectedExecutionException"); + } + } + + f.get(); + + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Test when a command fails to get queued up in the threadpool where the command implemented getFallback. + *

+ * We specifically want to protect against developers getting random thread exceptions and instead just correctly receives a fallback. + */ + @Test + public void testRejectedThreadWithFallback() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Rejection-Fallback"); + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1); + + //command 1 will execute in threadpool (passing through the queue) + //command 2 will execute after spending time in the queue (after command1 completes) + //command 3 will get rejected, since it finds pool and queue both full + TestCommandRejection command1 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); + TestCommandRejection command2 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); + TestCommandRejection command3 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); + + Observable result1 = command1.observe(); + Observable result2 = command2.observe(); + + Thread.sleep(100); + //command3 should find queue filled, and get rejected + assertFalse(command3.execute()); + assertTrue(command3.isResponseRejected()); + assertFalse(command1.isResponseRejected()); + assertFalse(command2.isResponseRejected()); + assertTrue(command3.isResponseFromFallback()); + assertNotNull(command3.getExecutionException()); + + assertCommandExecutionEvents(command3, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_SUCCESS); + Observable.merge(result1, result2).toList().toBlocking().single(); //await the 2 latent commands + + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertSaneHystrixRequestLog(3); + } + + /** + * Test when a command fails to get queued up in the threadpool where the command implemented getFallback but it fails. + *

+ * We specifically want to protect against developers getting random thread exceptions and instead just correctly receives an HystrixRuntimeException. + */ + @Test + public void testRejectedThreadWithFallbackFailure() throws ExecutionException, InterruptedException { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Rejection-A"); + + TestCommandRejection command1 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE); //this should pass through the queue and sit in the pool + TestCommandRejection command2 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); //this should sit in the queue + TestCommandRejection command3 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE); //this should observe full queue and get rejected + Future f1 = null; + Future f2 = null; + try { + f1 = command1.queue(); + f2 = command2.queue(); + assertEquals(false, command3.queue().get()); //should get thread-pool rejected + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + if (e instanceof HystrixRuntimeException && e.getCause() instanceof RejectedExecutionException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertFalse(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof RejectedExecutionException); + } else { + fail("the exception should be HystrixRuntimeException with cause as RejectedExecutionException"); + } + } + + assertCommandExecutionEvents(command1); //still in-flight, no events yet + assertCommandExecutionEvents(command2); //still in-flight, no events yet + assertCommandExecutionEvents(command3, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_FAILURE); + int numInFlight = circuitBreaker.metrics.getCurrentConcurrentExecutionCount(); + assertTrue("Expected at most 1 in flight but got : " + numInFlight, numInFlight <= 1); //pool-filler still going + //This is a case where we knowingly walk away from executing Hystrix threads. They should have an in-flight status ("Executed"). You should avoid this in a production environment + HystrixRequestLog requestLog = HystrixRequestLog.getCurrentRequest(); + assertEquals(3, requestLog.getAllExecutedCommands().size()); + assertTrue(requestLog.getExecutedCommandsAsString().contains("Executed")); + + //block on the outstanding work, so we don't inadvertently affect any other tests + long startTime = System.currentTimeMillis(); + f1.get(); + f2.get(); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + System.out.println("Time blocked : " + (System.currentTimeMillis() - startTime)); + } + + /** + * Test that we can reject a thread using isQueueSpaceAvailable() instead of just when the pool rejects. + *

+ * For example, we have queue size set to 100 but want to reject when we hit 10. + *

+ * This allows us to use FastProperties to control our rejection point whereas we can't resize a queue after it's created. + */ + @Test + public void testRejectedThreadUsingQueueSize() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Rejection-B"); + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(10, 1); + // put 1 item in the queue + // the thread pool won't pick it up because we're bypassing the pool and adding to the queue directly so this will keep the queue full + + pool.queue.add(new Runnable() { + + @Override + public void run() { + System.out.println("**** queue filler1 ****"); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + }); + + + TestCommandRejection command = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + try { + // this should fail as we already have 1 in the queue + command.queue(); + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + + assertTrue(command.isResponseRejected()); + assertFalse(command.isResponseShortCircuited()); + assertFalse(command.isResponseTimedOut()); + assertNotNull(command.getExecutionException()); + + if (e instanceof HystrixRuntimeException && e.getCause() instanceof RejectedExecutionException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof RejectedExecutionException); + } else { + fail("the exception should be HystrixRuntimeException with cause as RejectedExecutionException"); + } + } + + assertCommandExecutionEvents(command, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testDisabledTimeoutWorks() { + CommandWithDisabledTimeout cmd = new CommandWithDisabledTimeout(100, 900); + boolean result = cmd.execute(); + + assertEquals(true, result); + assertFalse(cmd.isResponseTimedOut()); + assertNull(cmd.getExecutionException()); + System.out.println("CMD : " + cmd.currentRequestLog.getExecutedCommandsAsString()); + assertTrue(cmd.executionResult.getExecutionLatency() >= 900); + assertCommandExecutionEvents(cmd, HystrixEventType.SUCCESS); + } + + @Test + public void testFallbackSemaphore() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + // single thread should work + TestSemaphoreCommandWithSlowFallback command1 = new TestSemaphoreCommandWithSlowFallback(circuitBreaker, 1, 200); + boolean result = command1.queue().get(); + assertTrue(result); + + // 2 threads, the second should be rejected by the fallback semaphore + boolean exceptionReceived = false; + Future result2 = null; + TestSemaphoreCommandWithSlowFallback command2 = null; + TestSemaphoreCommandWithSlowFallback command3 = null; + try { + System.out.println("c2 start: " + System.currentTimeMillis()); + command2 = new TestSemaphoreCommandWithSlowFallback(circuitBreaker, 1, 800); + result2 = command2.queue(); + System.out.println("c2 after queue: " + System.currentTimeMillis()); + // make sure that thread gets a chance to run before queuing the next one + Thread.sleep(50); + System.out.println("c3 start: " + System.currentTimeMillis()); + command3 = new TestSemaphoreCommandWithSlowFallback(circuitBreaker, 1, 200); + Future result3 = command3.queue(); + System.out.println("c3 after queue: " + System.currentTimeMillis()); + result3.get(); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived = true; + } + + assertTrue(result2.get()); + + if (!exceptionReceived) { + fail("We expected an exception on the 2nd get"); + } + + assertCommandExecutionEvents(command1, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_REJECTION); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + @Test + public void testExecutionSemaphoreWithQueue() throws Exception { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + // single thread should work + TestSemaphoreCommand command1 = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + boolean result = command1.queue().get(); + assertTrue(result); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + final TryableSemaphore semaphore = + new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); + + final TestSemaphoreCommand command2 = new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + command2.queue().get(); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + final TestSemaphoreCommand command3 = new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + Runnable r3 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + command3.queue().get(); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore + Thread t2 = new Thread(r2); + Thread t3 = new Thread(r3); + + t2.start(); + // make sure that t2 gets a chance to run before queuing the next one + Thread.sleep(50); + t3.start(); + t2.join(); + t3.join(); + + if (!exceptionReceived.get()) { + fail("We expected an exception on the 2nd get"); + } + + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + @Test + public void testExecutionSemaphoreWithExecution() throws Exception { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + // single thread should work + TestSemaphoreCommand command1 = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + boolean result = command1.execute(); + assertFalse(command1.isExecutedInThread()); + assertTrue(result); + + final ArrayBlockingQueue results = new ArrayBlockingQueue(2); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + final TryableSemaphore semaphore = + new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); + + final TestSemaphoreCommand command2 = new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(command2.execute()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + final TestSemaphoreCommand command3 = new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + Runnable r3 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(command3.execute()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore + Thread t2 = new Thread(r2); + Thread t3 = new Thread(r3); + + t2.start(); + // make sure that t2 gets a chance to run before queuing the next one + Thread.sleep(50); + t3.start(); + t2.join(); + t3.join(); + + if (!exceptionReceived.get()) { + fail("We expected an exception on the 2nd get"); + } + + // only 1 value is expected as the other should have thrown an exception + assertEquals(1, results.size()); + // should contain only a true result + assertTrue(results.contains(Boolean.TRUE)); + assertFalse(results.contains(Boolean.FALSE)); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + @Test + public void testRejectedExecutionSemaphoreWithFallbackViaExecute() throws Exception { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + final ArrayBlockingQueue results = new ArrayBlockingQueue(2); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + final TestSemaphoreCommandWithFallback command1 = new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false); + Runnable r1 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(command1.execute()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + final TestSemaphoreCommandWithFallback command2 = new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false); + Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(command2.execute()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore and return fallback + Thread t1 = new Thread(r1); + Thread t2 = new Thread(r2); + + t1.start(); + // make sure that t2 gets a chance to run before queuing the next one + Thread.sleep(50); + t2.start(); + t1.join(); + t2.join(); + + if (exceptionReceived.get()) { + fail("We should have received a fallback response"); + } + + // both threads should have returned values + assertEquals(2, results.size()); + // should contain both a true and false result + assertTrue(results.contains(Boolean.TRUE)); + assertTrue(results.contains(Boolean.FALSE)); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + @Test + public void testRejectedExecutionSemaphoreWithFallbackViaObserve() throws Exception { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + final ArrayBlockingQueue> results = new ArrayBlockingQueue>(2); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + final TestSemaphoreCommandWithFallback command1 = new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false); + Runnable r1 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(command1.observe()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + final TestSemaphoreCommandWithFallback command2 = new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false); + Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(command2.observe()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore and return fallback + Thread t1 = new Thread(r1); + Thread t2 = new Thread(r2); + + t1.start(); + // make sure that t2 gets a chance to run before queuing the next one + Thread.sleep(50); + t2.start(); + t1.join(); + t2.join(); + + if (exceptionReceived.get()) { + fail("We should have received a fallback response"); + } + + final List blockingList = Observable.merge(results).toList().toBlocking().single(); + + // both threads should have returned values + assertEquals(2, blockingList.size()); + // should contain both a true and false result + assertTrue(blockingList.contains(Boolean.TRUE)); + assertTrue(blockingList.contains(Boolean.FALSE)); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Tests that semaphores are counted separately for commands with unique keys + */ + @Test + public void testSemaphorePermitsInUse() throws Exception { + 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() * 2) + 1); + + // used to signal that all command can finish + final CountDownLatch sharedLatch = new CountDownLatch(1); + + // tracks failures to obtain semaphores + final AtomicInteger failureCount = new AtomicInteger(); + + final Runnable sharedSemaphoreRunnable = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + public void run() { + try { + new LatchedSemaphoreCommand("Command-Shared", circuitBreaker, sharedSemaphore, startLatch, sharedLatch).execute(); + } catch (Exception e) { + startLatch.countDown(); + e.printStackTrace(); + failureCount.incrementAndGet(); + } + } + }); + + // 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); + + final Thread isolatedThread = new Thread(new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + public void run() { + try { + new LatchedSemaphoreCommand("Command-Isolated", circuitBreaker, isolatedSemaphore, startLatch, isolatedLatch).execute(); + } catch (Exception e) { + startLatch.countDown(); + e.printStackTrace(); + failureCount.incrementAndGet(); + } + } + })); + + // verifies no permits in use before starting threads + assertEquals("before threads start, shared semaphore should be unused", 0, sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("before threads start, isolated semaphore should be unused", 0, isolatedSemaphore.getNumberOfPermitsUsed()); + + for (int i = 0; i < sharedThreadCount; i++) { + sharedSemaphoreThreads[i].start(); + } + isolatedThread.start(); + + // waits until all commands have started + startLatch.await(1000, TimeUnit.MILLISECONDS); + + // verifies that all semaphores are in use + assertEquals("immediately after command start, all shared semaphores should be in-use", + sharedSemaphore.numberOfPermits.get().longValue(), sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("immediately after command start, isolated semaphore should be in-use", + isolatedSemaphore.numberOfPermits.get().longValue(), isolatedSemaphore.getNumberOfPermitsUsed()); + + // signals commands to finish + sharedLatch.countDown(); + isolatedLatch.countDown(); + + for (int i = 0; i < sharedThreadCount; i++) { + sharedSemaphoreThreads[i].join(); + } + isolatedThread.join(); + + // verifies no permits in use after finishing threads + System.out.println("REQLOG : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + + assertEquals("after all threads have finished, no shared semaphores should be in-use", 0, sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("after all threads have finished, isolated semaphore not in-use", 0, isolatedSemaphore.getNumberOfPermitsUsed()); + + // verifies that some executions failed + assertEquals("expected some of shared semaphore commands to get rejected", sharedSemaphore.numberOfPermits.get().longValue(), failureCount.get()); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + } + + /** + * Test that HystrixOwner can be passed in dynamically. + */ + @Test + public void testDynamicOwner() { + TestHystrixCommand command = new DynamicOwnerTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE); + assertEquals(true, command.execute()); + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS); + } + + /** + * Test a successful command execution. + */ + @Test(expected = IllegalStateException.class) + public void testDynamicOwnerFails() { + TestHystrixCommand command = new DynamicOwnerTestCommand(null); + assertEquals(true, command.execute()); + } + + /** + * Test that HystrixCommandKey can be passed in dynamically. + */ + @Test + public void testDynamicKey() throws Exception { + DynamicOwnerAndKeyTestCommand command1 = new DynamicOwnerAndKeyTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE, InspectableBuilder.CommandKeyForUnitTest.KEY_ONE); + assertEquals(true, command1.execute()); + DynamicOwnerAndKeyTestCommand command2 = new DynamicOwnerAndKeyTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE, InspectableBuilder.CommandKeyForUnitTest.KEY_TWO); + assertEquals(true, command2.execute()); + + // 2 different circuit breakers should be created + assertNotSame(command1.getCircuitBreaker(), command2.getCircuitBreaker()); + } + + /** + * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future + */ + @Test + public void testRequestCache1() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + + assertEquals("A", f1.get()); + assertEquals("A", f2.get()); + + assertTrue(command1.executed); + // the second one should not have executed as it should have received the cached value instead + assertFalse(command2.executed); + assertTrue(command1.getExecutionTimeInMilliseconds() > -1); + assertFalse(command1.isResponseFromCache()); + assertTrue(command2.isResponseFromCache()); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Test Request scoped caching doesn't prevent different ones from executing + */ + @Test + public void testRequestCache2() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "B"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + assertTrue(command2.getExecutionTimeInMilliseconds() > -1); + assertFalse(command2.isResponseFromCache()); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertNull(command1.getExecutionException()); + assertFalse(command2.isResponseFromCache()); + assertNull(command2.getExecutionException()); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testRequestCache3() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "B"); + SuccessfulCacheableCommand command3 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + Future f3 = command3.queue(); + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // but the 3rd should come from cache + assertFalse(command3.executed); + assertTrue(command3.isResponseFromCache()); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertSaneHystrixRequestLog(3); + } + + /** + * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future + */ + @Test + public void testRequestCacheWithSlowExecution() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SlowCacheableCommand command1 = new SlowCacheableCommand(circuitBreaker, "A", 200); + SlowCacheableCommand command2 = new SlowCacheableCommand(circuitBreaker, "A", 100); + SlowCacheableCommand command3 = new SlowCacheableCommand(circuitBreaker, "A", 100); + SlowCacheableCommand command4 = new SlowCacheableCommand(circuitBreaker, "A", 100); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + Future f3 = command3.queue(); + Future f4 = command4.queue(); + + assertEquals("A", f2.get()); + assertEquals("A", f3.get()); + assertEquals("A", f4.get()); + assertEquals("A", f1.get()); + + assertTrue(command1.executed); + // the second one should not have executed as it should have received the cached value instead + assertFalse(command2.executed); + assertFalse(command3.executed); + assertFalse(command4.executed); + + assertTrue(command1.getExecutionTimeInMilliseconds() > -1); + assertFalse(command1.isResponseFromCache()); + assertTrue(command2.getExecutionTimeInMilliseconds() == -1); + assertTrue(command2.isResponseFromCache()); + assertTrue(command3.isResponseFromCache()); + assertTrue(command3.getExecutionTimeInMilliseconds() == -1); + assertTrue(command4.isResponseFromCache()); + assertTrue(command4.getExecutionTimeInMilliseconds() == -1); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(command4, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(4); + System.out.println("HystrixRequestLog: " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testNoRequestCache3() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, false, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, false, "B"); + SuccessfulCacheableCommand command3 = new SuccessfulCacheableCommand(circuitBreaker, false, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + Future f3 = command3.queue(); + + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // this should also execute since we disabled the cache + assertTrue(command3.executed); + + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testRequestCacheViaQueueSemaphore1() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommandViaSemaphore command1 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "A"); + SuccessfulCacheableCommandViaSemaphore command2 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "B"); + SuccessfulCacheableCommandViaSemaphore command3 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "A"); + + assertFalse(command1.isCommandRunningInThread()); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + Future f3 = command3.queue(); + + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // but the 3rd should come from cache + assertFalse(command3.executed); + assertTrue(command3.isResponseFromCache()); + assertTrue(command3.getExecutionTimeInMilliseconds() == -1); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testNoRequestCacheViaQueueSemaphore1() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommandViaSemaphore command1 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "A"); + SuccessfulCacheableCommandViaSemaphore command2 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "B"); + SuccessfulCacheableCommandViaSemaphore command3 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "A"); + + assertFalse(command1.isCommandRunningInThread()); + + Future f1 = command1.queue(); + Future f2 = command2.queue(); + Future f3 = command3.queue(); + + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // this should also execute because caching is disabled + assertTrue(command3.executed); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testRequestCacheViaExecuteSemaphore1() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommandViaSemaphore command1 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "A"); + SuccessfulCacheableCommandViaSemaphore command2 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "B"); + SuccessfulCacheableCommandViaSemaphore command3 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "A"); + + assertFalse(command1.isCommandRunningInThread()); + + String f1 = command1.execute(); + String f2 = command2.execute(); + String f3 = command3.execute(); + + assertEquals("A", f1); + assertEquals("B", f2); + assertEquals("A", f3); + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // but the 3rd should come from cache + assertFalse(command3.executed); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testNoRequestCacheViaExecuteSemaphore1() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommandViaSemaphore command1 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "A"); + SuccessfulCacheableCommandViaSemaphore command2 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "B"); + SuccessfulCacheableCommandViaSemaphore command3 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "A"); + + assertFalse(command1.isCommandRunningInThread()); + + String f1 = command1.execute(); + String f2 = command2.execute(); + String f3 = command3.execute(); + + assertEquals("A", f1); + assertEquals("B", f2); + assertEquals("A", f3); + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // this should also execute because caching is disabled + assertTrue(command3.executed); + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + @Test + public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + NoRequestCacheTimeoutWithoutFallback r1 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + System.out.println("r1 value: " + r1.execute()); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r1.isResponseTimedOut()); + // what we want + } + + NoRequestCacheTimeoutWithoutFallback r2 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r2.execute(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r2.isResponseTimedOut()); + // what we want + } + + NoRequestCacheTimeoutWithoutFallback r3 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + Future f3 = r3.queue(); + try { + f3.get(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (ExecutionException e) { + e.printStackTrace(); + assertTrue(r3.isResponseTimedOut()); + // what we want + } + + Thread.sleep(500); // timeout on command is set to 200ms + + NoRequestCacheTimeoutWithoutFallback r4 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r4.execute(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r4.isResponseTimedOut()); + assertFalse(r4.isResponseFromFallback()); + // what we want + } + + assertCommandExecutionEvents(r1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(4); + } + + @Test + public void testRequestCacheOnTimeoutCausesNullPointerException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + RequestCacheNullPointerExceptionCase command1 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + RequestCacheNullPointerExceptionCase command2 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + RequestCacheNullPointerExceptionCase command3 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + + // Expect it to time out - all results should be false + assertFalse(command1.execute()); + assertFalse(command2.execute()); // return from cache #1 + assertFalse(command3.execute()); // return from cache #2 + Thread.sleep(500); // timeout on command is set to 200ms + + RequestCacheNullPointerExceptionCase command4 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + Boolean value = command4.execute(); // return from cache #3 + assertFalse(value); + RequestCacheNullPointerExceptionCase command5 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + Future f = command5.queue(); // return from cache #4 + // the bug is that we're getting a null Future back, rather than a Future that returns false + assertNotNull(f); + assertFalse(f.get()); + + assertTrue(command5.isResponseFromFallback()); + assertTrue(command5.isResponseTimedOut()); + assertFalse(command5.isFailedExecution()); + assertFalse(command5.isResponseShortCircuited()); + assertNotNull(command5.getExecutionException()); + + assertCommandExecutionEvents(command1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(command3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(command4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(command5, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(5); + } + + @Test + public void testRequestCacheOnTimeoutThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + RequestCacheTimeoutWithoutFallback r1 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + System.out.println("r1 value: " + r1.execute()); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r1.isResponseTimedOut()); + // what we want + } + + RequestCacheTimeoutWithoutFallback r2 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r2.execute(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r2.isResponseTimedOut()); + // what we want + } + + RequestCacheTimeoutWithoutFallback r3 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + Future f3 = r3.queue(); + try { + f3.get(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (ExecutionException e) { + e.printStackTrace(); + assertTrue(r3.isResponseTimedOut()); + // what we want + } + + Thread.sleep(500); // timeout on command is set to 200ms + + RequestCacheTimeoutWithoutFallback r4 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r4.execute(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r4.isResponseTimedOut()); + assertFalse(r4.isResponseFromFallback()); + // what we want + } + + assertCommandExecutionEvents(r1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(r3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(r4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(4); + } + + @Test + public void testRequestCacheOnThreadRejectionThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CountDownLatch completionLatch = new CountDownLatch(1); + RequestCacheThreadRejectionWithoutFallback r1 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r1: " + r1.execute()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + assertTrue(r1.isResponseRejected()); + // what we want + } + + RequestCacheThreadRejectionWithoutFallback r2 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r2: " + r2.execute()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + // e.printStackTrace(); + assertTrue(r2.isResponseRejected()); + // what we want + } + + RequestCacheThreadRejectionWithoutFallback r3 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("f3: " + r3.queue().get()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + // e.printStackTrace(); + assertTrue(r3.isResponseRejected()); + // what we want + } + + // let the command finish (only 1 should actually be blocked on this due to the response cache) + completionLatch.countDown(); + + // then another after the command has completed + RequestCacheThreadRejectionWithoutFallback r4 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r4: " + r4.execute()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + // e.printStackTrace(); + assertTrue(r4.isResponseRejected()); + assertFalse(r4.isResponseFromFallback()); + // what we want + } + + assertCommandExecutionEvents(r1, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r2, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(r3, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(r4, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(4); + } + + /** + * Test that we can do basic execution without a RequestVariable being initialized. + */ + @Test + public void testBasicExecutionWorksWithoutRequestVariable() throws Exception { + /* force the RequestVariable to not be initialized */ + HystrixRequestContext.setContextOnCurrentThread(null); + + TestHystrixCommand command = new SuccessfulTestCommand(); + assertEquals(true, command.execute()); + + TestHystrixCommand command2 = new SuccessfulTestCommand(); + assertEquals(true, command2.queue().get()); + } + + /** + * Test that if we try and execute a command with a cacheKey without initializing RequestVariable that it gives an error. + */ + @Test(expected = HystrixRuntimeException.class) + public void testCacheKeyExecutionRequiresRequestVariable() throws Exception { + /* force the RequestVariable to not be initialized */ + HystrixRequestContext.setContextOnCurrentThread(null); + + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + + SuccessfulCacheableCommand command = new SuccessfulCacheableCommand(circuitBreaker, true, "one"); + assertEquals("one", command.execute()); + + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "two"); + assertEquals("two", command2.queue().get()); + } + + /** + * Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback. + */ + @Test + public void testBadRequestExceptionViaExecuteInThread() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + BadRequestCommand command1 = null; + try { + command1 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD); + command1.execute(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (HystrixBadRequestException e) { + // success + e.printStackTrace(); + } + + assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback. + */ + @Test + public void testBadRequestExceptionViaQueueInThread() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + BadRequestCommand command1 = null; + try { + command1 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD); + command1.queue().get(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (ExecutionException e) { + e.printStackTrace(); + if (e.getCause() instanceof HystrixBadRequestException) { + // success + } else { + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + } + + assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST); + assertNotNull(command1.getExecutionException()); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test that BadRequestException behavior works the same on a cached response. + */ + @Test + public void testBadRequestExceptionViaQueueInThreadOnResponseFromCache() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + + // execute once to cache the value + BadRequestCommand command1 = null; + try { + command1 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD); + command1.execute(); + } catch (Throwable e) { + // ignore + } + + BadRequestCommand command2 = null; + try { + command2 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD); + command2.queue().get(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (ExecutionException e) { + e.printStackTrace(); + if (e.getCause() instanceof HystrixBadRequestException) { + // success + } else { + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + } + + assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST); + assertCommandExecutionEvents(command2, HystrixEventType.BAD_REQUEST, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback. + */ + @Test + public void testBadRequestExceptionViaExecuteInSemaphore() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + BadRequestCommand command1 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE); + try { + command1.execute(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (HystrixBadRequestException e) { + // success + e.printStackTrace(); + } + + assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a checked Exception being thrown + */ + @Test + public void testCheckedExceptionViaExecute() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker); + try { + command.execute(); + fail("we expect to receive a " + Exception.class.getSimpleName()); + } catch (Exception e) { + assertEquals("simulated checked exception message", e.getCause().getMessage()); + } + + assertEquals("simulated checked exception message", command.getFailedExecutionException().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a java.lang.Error being thrown + * + * @throws InterruptedException + */ + @Test + public void testCheckedExceptionViaObserve() throws InterruptedException { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker); + final AtomicReference t = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + try { + command.observe().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + t.set(e); + latch.countDown(); + } + + @Override + public void onNext(Boolean args) { + + } + + }); + } catch (Exception e) { + e.printStackTrace(); + fail("we should not get anything thrown, it should be emitted via the Observer#onError method"); + } + + latch.await(1, TimeUnit.SECONDS); + assertNotNull(t.get()); + t.get().printStackTrace(); + + assertTrue(t.get() instanceof HystrixRuntimeException); + assertEquals("simulated checked exception message", t.get().getCause().getMessage()); + assertEquals("simulated checked exception message", command.getFailedExecutionException().getMessage()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test an Exception implementing NotWrappedByHystrix being thrown + * + * @throws InterruptedException + */ + @Test + public void testNotWrappedExceptionViaObserve() throws InterruptedException { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithNotWrappedByHystrixException command = new CommandWithNotWrappedByHystrixException(circuitBreaker); + final AtomicReference t = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + try { + command.observe().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + t.set(e); + latch.countDown(); + } + + @Override + public void onNext(Boolean args) { + + } + + }); + } catch (Exception e) { + e.printStackTrace(); + fail("we should not get anything thrown, it should be emitted via the Observer#onError method"); + } + + latch.await(1, TimeUnit.SECONDS); + assertNotNull(t.get()); + t.get().printStackTrace(); + + assertTrue(t.get() instanceof NotWrappedByHystrixTestException); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertTrue(command.getExecutionException() instanceof NotWrappedByHystrixTestException); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testSemaphoreExecutionWithTimeout() { + TestHystrixCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), false); + + System.out.println("Starting command"); + long timeMillis = System.currentTimeMillis(); + try { + cmd.execute(); + fail("Should throw"); + } catch (Throwable t) { + assertNotNull(cmd.getExecutionException()); + + System.out.println("Unsuccessful Execution took : " + (System.currentTimeMillis() - timeMillis)); + assertCommandExecutionEvents(cmd, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + } + + /** + * Test a recoverable java.lang.Error being thrown with no fallback + */ + @Test + public void testRecoverableErrorWithNoFallbackThrowsError() { + TestHystrixCommand command = getRecoverableErrorCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + try { + command.execute(); + fail("we expect to receive a " + Error.class.getSimpleName()); + } catch (Exception e) { + // the actual error is an extra cause level deep because Hystrix needs to wrap Throwable/Error as it's public + // methods only support Exception and it's not a strong enough reason to break backwards compatibility and jump to version 2.x + // so HystrixRuntimeException -> wrapper Exception -> actual Error + assertEquals("Execution ERROR for TestHystrixCommand", e.getCause().getCause().getMessage()); + } + + assertEquals("Execution ERROR for TestHystrixCommand", command.getFailedExecutionException().getCause().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testRecoverableErrorMaskedByFallbackButLogged() { + TestHystrixCommand command = getRecoverableErrorCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.execute()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testUnrecoverableErrorThrownWithNoFallback() { + TestHystrixCommand command = getUnrecoverableErrorCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + try { + command.execute(); + fail("we expect to receive a " + Error.class.getSimpleName()); + } catch (Exception e) { + // the actual error is an extra cause level deep because Hystrix needs to wrap Throwable/Error as it's public + // methods only support Exception and it's not a strong enough reason to break backwards compatibility and jump to version 2.x + // so HystrixRuntimeException -> wrapper Exception -> actual Error + assertEquals("Unrecoverable Error for TestHystrixCommand", e.getCause().getCause().getMessage()); + } + + assertEquals("Unrecoverable Error for TestHystrixCommand", command.getFailedExecutionException().getCause().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test //even though fallback is implemented, that logic never fires, as this is an unrecoverable error and should be directly propagated to the caller + public void testUnrecoverableErrorThrownWithFallback() { + TestHystrixCommand command = getUnrecoverableErrorCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + try { + command.execute(); + fail("we expect to receive a " + Error.class.getSimpleName()); + } catch (Exception e) { + // the actual error is an extra cause level deep because Hystrix needs to wrap Throwable/Error as it's public + // methods only support Exception and it's not a strong enough reason to break backwards compatibility and jump to version 2.x + // so HystrixRuntimeException -> wrapper Exception -> actual Error + assertEquals("Unrecoverable Error for TestHystrixCommand", e.getCause().getCause().getMessage()); + } + + assertEquals("Unrecoverable Error for TestHystrixCommand", command.getFailedExecutionException().getCause().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + static class EventCommand extends HystrixCommand { + public EventCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("eventGroup")).andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withFallbackIsolationSemaphoreMaxConcurrentRequests(3))); + } + + @Override + protected String run() throws Exception { + System.out.println(Thread.currentThread().getName() + " : In run()"); + throw new RuntimeException("run_exception"); + } + + @Override + public String getFallback() { + try { + System.out.println(Thread.currentThread().getName() + " : In fallback => " + getExecutionEvents()); + Thread.sleep(30000L); + } catch (InterruptedException e) { + System.out.println(Thread.currentThread().getName() + " : Interruption occurred"); + } + System.out.println(Thread.currentThread().getName() + " : CMD Success Result"); + return "fallback"; + } + } + + @Test + public void testNonBlockingCommandQueueFiresTimeout() throws Exception { //see https://github.com/Netflix/Hystrix/issues/514 + final TestHystrixCommand cmd = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50); + + new Thread() { + @Override + public void run() { + cmd.queue(); + } + }.start(); + + Thread.sleep(200); + //timeout should occur in 50ms, and underlying thread should run for 500ms + //therefore, after 200ms, the command should have finished with a fallback on timeout + + assertTrue(cmd.isExecutionComplete()); + assertTrue(cmd.isResponseTimedOut()); + + assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount()); + } + + @Override + protected void assertHooksOnSuccess(Func0> ctor, Action1> assertion) { + assertExecute(ctor.call(), assertion, true); + assertBlockingQueue(ctor.call(), assertion, true); + assertNonBlockingQueue(ctor.call(), assertion, true, false); + assertBlockingObserve(ctor.call(), assertion, true); + assertNonBlockingObserve(ctor.call(), assertion, true); + } + + @Override + protected void assertHooksOnFailure(Func0> ctor, Action1> assertion) { + assertExecute(ctor.call(), assertion, false); + assertBlockingQueue(ctor.call(), assertion, false); + assertNonBlockingQueue(ctor.call(), assertion, false, false); + assertBlockingObserve(ctor.call(), assertion, false); + assertNonBlockingObserve(ctor.call(), assertion, false); + } + + @Override + protected void assertHooksOnFailure(Func0> ctor, Action1> assertion, boolean failFast) { + assertExecute(ctor.call(), assertion, false); + assertBlockingQueue(ctor.call(), assertion, false); + assertNonBlockingQueue(ctor.call(), assertion, false, failFast); + assertBlockingObserve(ctor.call(), assertion, false); + assertNonBlockingObserve(ctor.call(), assertion, false); + } + + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#execute()} and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeedInteger + */ + private void assertExecute(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Running command.execute() and then assertions..."); + if (isSuccess) { + command.execute(); + } else { + try { + Object o = command.execute(); + fail("Expected a command failure!"); + } catch (Exception ex) { + System.out.println("Received expected ex : " + ex); + ex.printStackTrace(); + } + } + + assertion.call(command); + } + + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#queue()}, immediately block, and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeedInteger + */ + private void assertBlockingQueue(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println("Running command.queue(), immediately blocking and then running assertions..."); + if (isSuccess) { + try { + command.queue().get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + try { + command.queue().get(); + fail("Expected a command failure!"); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } catch (ExecutionException ee) { + System.out.println("Received expected ex : " + ee.getCause()); + ee.getCause().printStackTrace(); + } catch (Exception e) { + System.out.println("Received expected ex : " + e); + e.printStackTrace(); + } + } + + assertion.call(command); + } + + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#queue()}, then poll for the command to be finished. + * When it is finished, assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeedInteger + */ + private void assertNonBlockingQueue(TestHystrixCommand command, Action1> assertion, boolean isSuccess, boolean failFast) { + System.out.println("Running command.queue(), sleeping the test thread until command is complete, and then running assertions..."); + Future f = null; + if (failFast) { + try { + f = command.queue(); + fail("Expected a failure when queuing the command"); + } catch (Exception ex) { + System.out.println("Received expected fail fast ex : " + ex); + ex.printStackTrace(); + } + } else { + try { + f = command.queue(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + awaitCommandCompletion(command); + + assertion.call(command); + + if (isSuccess) { + try { + f.get(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + try { + f.get(); + fail("Expected a command failure!"); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } catch (ExecutionException ee) { + System.out.println("Received expected ex : " + ee.getCause()); + ee.getCause().printStackTrace(); + } catch (Exception e) { + System.out.println("Received expected ex : " + e); + e.printStackTrace(); + } + } + } + + private void awaitCommandCompletion(TestHystrixCommand command) { + while (!command.isExecutionComplete()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException("interrupted"); + } + } + } + + /** + * Test a command execution that fails but has a fallback. + */ + @Test + public void testExecutionFailureWithFallbackImplementedButDisabled() { + TestHystrixCommand commandEnabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), true); + try { + assertEquals(false, commandEnabled.execute()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + TestHystrixCommand commandDisabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), false); + try { + assertEquals(false, commandDisabled.execute()); + fail("expect exception thrown"); + } catch (Exception e) { + // expected + } + + assertEquals("we failed with a simulated issue", commandDisabled.getFailedExecutionException().getMessage()); + + assertTrue(commandDisabled.isFailedExecution()); + assertCommandExecutionEvents(commandEnabled, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(commandDisabled, HystrixEventType.FAILURE); + assertNotNull(commandDisabled.getExecutionException()); + assertEquals(0, commandDisabled.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + @Test + public void testExecutionTimeoutValue() { + HystrixCommand.Setter properties = HystrixCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionTimeoutInMilliseconds(50)); + + HystrixCommand command = new HystrixCommand(properties) { + @Override + protected String run() throws Exception { + Thread.sleep(3000); + // should never reach here + return "hello"; + } + + @Override + protected String getFallback() { + if (isResponseTimedOut()) { + return "timed-out"; + } else { + return "abc"; + } + } + }; + + String value = command.execute(); + assertTrue(command.isResponseTimedOut()); + assertEquals("expected fallback value", "timed-out", value); + + } + + /** + * See https://github.com/Netflix/Hystrix/issues/212 + */ + @Test + public void testObservableTimeoutNoFallbackThreadContext() { + TestSubscriber ts = new TestSubscriber(); + + final AtomicReference onErrorThread = new AtomicReference(); + final AtomicBoolean isRequestContextInitialized = new AtomicBoolean(); + + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50); + command.toObservable().doOnError(new Action1() { + + @Override + public void call(Throwable t1) { + System.out.println("onError: " + t1); + System.out.println("onError Thread: " + Thread.currentThread()); + System.out.println("ThreadContext in onError: " + HystrixRequestContext.isCurrentThreadInitialized()); + onErrorThread.set(Thread.currentThread()); + isRequestContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + } + + }).subscribe(ts); + + ts.awaitTerminalEvent(); + + assertTrue(isRequestContextInitialized.get()); + assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); + + List errors = ts.getOnErrorEvents(); + assertEquals(1, errors.size()); + Throwable e = errors.get(0); + if (errors.get(0) instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be ExecutionException with cause as HystrixRuntimeException"); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isResponseTimedOut()); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testExceptionConvertedToBadRequestExceptionInExecutionHookBypassesCircuitBreaker() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + ExceptionToBadRequestByExecutionHookCommand command = new ExceptionToBadRequestByExecutionHookCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD); + try { + command.execute(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (HystrixBadRequestException e) { + // success + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + + assertCommandExecutionEvents(command, HystrixEventType.BAD_REQUEST); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testInterruptFutureOnTimeout() throws InterruptedException, ExecutionException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true); + + // when + Future f = cmd.queue(); + + // then + Thread.sleep(500); + assertTrue(cmd.hasBeenInterrupted()); + } + + @Test + public void testInterruptObserveOnTimeout() throws InterruptedException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true); + + // when + cmd.observe().subscribe(); + + // then + Thread.sleep(500); + assertTrue(cmd.hasBeenInterrupted()); + } + + @Test + public void testInterruptToObservableOnTimeout() throws InterruptedException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true); + + // when + cmd.toObservable().subscribe(); + + // then + Thread.sleep(500); + assertTrue(cmd.hasBeenInterrupted()); + } + + @Test + public void testDoNotInterruptFutureOnTimeoutIfPropertySaysNotTo() throws InterruptedException, ExecutionException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), false); + + // when + Future f = cmd.queue(); + + // then + Thread.sleep(500); + assertFalse(cmd.hasBeenInterrupted()); + } + + @Test + public void testDoNotInterruptObserveOnTimeoutIfPropertySaysNotTo() throws InterruptedException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), false); + + // when + cmd.observe().subscribe(); + + // then + Thread.sleep(500); + assertFalse(cmd.hasBeenInterrupted()); + } + + @Test + public void testDoNotInterruptToObservableOnTimeoutIfPropertySaysNotTo() throws InterruptedException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), false); + + // when + cmd.toObservable().subscribe(); + + // then + Thread.sleep(500); + assertFalse(cmd.hasBeenInterrupted()); + } + + @Test + public void testCancelFutureWithInterruptionWhenPropertySaysNotTo() throws InterruptedException, ExecutionException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true, false, 1000); + + // when + Future f = cmd.queue(); + Thread.sleep(500); + f.cancel(true); + Thread.sleep(500); + + // then + try { + f.get(); + fail("Should have thrown a CancellationException"); + } catch (CancellationException e) { + assertFalse(cmd.hasBeenInterrupted()); + } + } + + @Test + public void testCancelFutureWithInterruption() throws InterruptedException, ExecutionException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true, true, 1000); + + // when + Future f = cmd.queue(); + Thread.sleep(500); + f.cancel(true); + Thread.sleep(500); + + // then + try { + f.get(); + fail("Should have thrown a CancellationException"); + } catch (CancellationException e) { + assertTrue(cmd.hasBeenInterrupted()); + } + } + + @Test + public void testCancelFutureWithoutInterruption() throws InterruptedException, ExecutionException, TimeoutException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true, true, 1000); + + // when + Future f = cmd.queue(); + Thread.sleep(500); + f.cancel(false); + Thread.sleep(500); + + // then + try { + f.get(); + fail("Should have thrown a CancellationException"); + } catch (CancellationException e) { + assertFalse(cmd.hasBeenInterrupted()); + } + } + + @Test + public void testChainedCommand() { + class SubCommand extends TestHystrixCommand { + + public SubCommand(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Integer run() throws Exception { + return 2; + } + } + + class PrimaryCommand extends TestHystrixCommand { + public PrimaryCommand(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Integer run() throws Exception { + throw new RuntimeException("primary failure"); + } + + @Override + protected Integer getFallback() { + SubCommand subCmd = new SubCommand(new TestCircuitBreaker()); + return subCmd.execute(); + } + } + + assertTrue(2 == new PrimaryCommand(new TestCircuitBreaker()).execute()); + } + + @Test + public void testSlowFallback() { + class PrimaryCommand extends TestHystrixCommand { + public PrimaryCommand(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Integer run() throws Exception { + throw new RuntimeException("primary failure"); + } + + @Override + protected Integer getFallback() { + try { + Thread.sleep(1500); + return 1; + } catch (InterruptedException ie) { + System.out.println("Caught Interrupted Exception"); + ie.printStackTrace(); + } + return -1; + } + } + + assertTrue(1 == new PrimaryCommand(new TestCircuitBreaker()).execute()); + } + + @Test + public void testSemaphoreThreadSafety() { + final int NUM_PERMITS = 1; + final TryableSemaphoreActual s = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(NUM_PERMITS)); + + final int NUM_THREADS = 10; + ExecutorService threadPool = Executors.newFixedThreadPool(NUM_THREADS); + + final int NUM_TRIALS = 100; + + for (int t = 0; t < NUM_TRIALS; t++) { + + System.out.println("TRIAL : " + t); + + final AtomicInteger numAcquired = new AtomicInteger(0); + final CountDownLatch latch = new CountDownLatch(NUM_THREADS); + + for (int i = 0; i < NUM_THREADS; i++) { + threadPool.submit(new Runnable() { + @Override + public void run() { + boolean acquired = s.tryAcquire(); + if (acquired) { + try { + numAcquired.incrementAndGet(); + Thread.sleep(100); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } finally { + s.release(); + } + } + latch.countDown(); + } + }); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + assertEquals("Number acquired should be equal to the number of permits", NUM_PERMITS, numAcquired.get()); + assertEquals("Semaphore should always get released back to 0", 0, s.getNumberOfPermitsUsed()); + } + } + + @Test + public void testCancelledTasksInQueueGetRemoved() throws Exception { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cancellation-A"); + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(10, 1); + TestCommandRejection command1 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + TestCommandRejection command2 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + + // this should go through the queue and into the thread pool + Future poolFiller = command1.queue(); + // this command will stay in the queue until the thread pool is empty + Observable cmdInQueue = command2.observe(); + Subscription s = cmdInQueue.subscribe(); + assertEquals(1, pool.queue.size()); + s.unsubscribe(); + assertEquals(0, pool.queue.size()); + //make sure we wait for the command to finish so the state is clean for next test + poolFiller.get(); + + assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.CANCELLED); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertSaneHystrixRequestLog(2); + } + + @Test + public void testOnRunStartHookThrowsSemaphoreIsolated() { + final AtomicBoolean exceptionEncountered = new AtomicBoolean(false); + final AtomicBoolean onThreadStartInvoked = new AtomicBoolean(false); + final AtomicBoolean onThreadCompleteInvoked = new AtomicBoolean(false); + final AtomicBoolean executionAttempted = new AtomicBoolean(false); + + class FailureInjectionHook extends HystrixCommandExecutionHook { + @Override + public void onExecutionStart(HystrixInvokable commandInstance) { + throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, commandInstance.getClass(), "Injected Failure", null, null); + } + + @Override + public void onThreadStart(HystrixInvokable commandInstance) { + onThreadStartInvoked.set(true); + super.onThreadStart(commandInstance); + } + + @Override + public void onThreadComplete(HystrixInvokable commandInstance) { + onThreadCompleteInvoked.set(true); + super.onThreadComplete(commandInstance); + } + } + + final FailureInjectionHook failureInjectionHook = new FailureInjectionHook(); + + class FailureInjectedCommand extends TestHystrixCommand { + public FailureInjectedCommand(ExecutionIsolationStrategy isolationStrategy) { + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)), failureInjectionHook); + } + + @Override + protected Integer run() throws Exception { + executionAttempted.set(true); + return 3; + } + } + + TestHystrixCommand semaphoreCmd = new FailureInjectedCommand(ExecutionIsolationStrategy.SEMAPHORE); + try { + int result = semaphoreCmd.execute(); + System.out.println("RESULT : " + result); + } catch (Throwable ex) { + ex.printStackTrace(); + exceptionEncountered.set(true); + } + assertTrue(exceptionEncountered.get()); + assertFalse(onThreadStartInvoked.get()); + assertFalse(onThreadCompleteInvoked.get()); + assertFalse(executionAttempted.get()); + assertEquals(0, semaphoreCmd.metrics.getCurrentConcurrentExecutionCount()); + + } + + @Test + public void testOnRunStartHookThrowsThreadIsolated() { + final AtomicBoolean exceptionEncountered = new AtomicBoolean(false); + final AtomicBoolean onThreadStartInvoked = new AtomicBoolean(false); + final AtomicBoolean onThreadCompleteInvoked = new AtomicBoolean(false); + final AtomicBoolean executionAttempted = new AtomicBoolean(false); + + class FailureInjectionHook extends HystrixCommandExecutionHook { + @Override + public void onExecutionStart(HystrixInvokable commandInstance) { + throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, commandInstance.getClass(), "Injected Failure", null, null); + } + + @Override + public void onThreadStart(HystrixInvokable commandInstance) { + onThreadStartInvoked.set(true); + super.onThreadStart(commandInstance); + } + + @Override + public void onThreadComplete(HystrixInvokable commandInstance) { + onThreadCompleteInvoked.set(true); + super.onThreadComplete(commandInstance); + } + } + + final FailureInjectionHook failureInjectionHook = new FailureInjectionHook(); + + class FailureInjectedCommand extends TestHystrixCommand { + public FailureInjectedCommand(ExecutionIsolationStrategy isolationStrategy) { + super(testPropsBuilder(new TestCircuitBreaker()).setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)), failureInjectionHook); + } + + @Override + protected Integer run() throws Exception { + executionAttempted.set(true); + return 3; + } + } + + TestHystrixCommand threadCmd = new FailureInjectedCommand(ExecutionIsolationStrategy.THREAD); + try { + int result = threadCmd.execute(); + System.out.println("RESULT : " + result); + } catch (Throwable ex) { + ex.printStackTrace(); + exceptionEncountered.set(true); + } + assertTrue(exceptionEncountered.get()); + assertTrue(onThreadStartInvoked.get()); + assertTrue(onThreadCompleteInvoked.get()); + assertFalse(executionAttempted.get()); + assertEquals(0, threadCmd.metrics.getCurrentConcurrentExecutionCount()); + + } + + @Test + public void testEarlyUnsubscribeDuringExecutionViaToObservable() { + class AsyncCommand extends HystrixCommand { + + public AsyncCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); + } + + @Override + protected Boolean run() { + try { + Thread.sleep(500); + return true; + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + } + + HystrixCommand cmd = new AsyncCommand(); + + final CountDownLatch latch = new CountDownLatch(1); + + Observable o = cmd.toObservable(); + Subscription s = o. + doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println("OnUnsubscribe"); + latch.countDown(); + } + }). + subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("OnCompleted"); + } + + @Override + public void onError(Throwable e) { + System.out.println("OnError : " + e); + } + + @Override + public void onNext(Boolean b) { + System.out.println("OnNext : " + b); + } + }); + + try { + Thread.sleep(10); + s.unsubscribe(); + assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(cmd.isExecutionComplete()); + assertEquals(null, cmd.getFailedExecutionException()); + assertNull(cmd.getExecutionException()); + System.out.println("Execution time : " + cmd.getExecutionTimeInMilliseconds()); + assertTrue(cmd.getExecutionTimeInMilliseconds() > -1); + assertFalse(cmd.isSuccessfulExecution()); + assertCommandExecutionEvents(cmd, HystrixEventType.CANCELLED); + assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + @Test + public void testEarlyUnsubscribeDuringExecutionViaObserve() { + class AsyncCommand extends HystrixCommand { + + public AsyncCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); + } + + @Override + protected Boolean run() { + try { + Thread.sleep(500); + return true; + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + } + + HystrixCommand cmd = new AsyncCommand(); + + final CountDownLatch latch = new CountDownLatch(1); + + Observable o = cmd.observe(); + Subscription s = o. + doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println("OnUnsubscribe"); + latch.countDown(); + } + }). + subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("OnCompleted"); + } + + @Override + public void onError(Throwable e) { + System.out.println("OnError : " + e); + } + + @Override + public void onNext(Boolean b) { + System.out.println("OnNext : " + b); + } + }); + + try { + Thread.sleep(10); + s.unsubscribe(); + assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(cmd.isExecutionComplete()); + assertEquals(null, cmd.getFailedExecutionException()); + assertNull(cmd.getExecutionException()); + assertTrue(cmd.getExecutionTimeInMilliseconds() > -1); + assertFalse(cmd.isSuccessfulExecution()); + assertCommandExecutionEvents(cmd, HystrixEventType.CANCELLED); + assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + @Test + public void testEarlyUnsubscribeDuringFallback() { + class AsyncCommand extends HystrixCommand { + + public AsyncCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); + } + + @Override + protected Boolean run() { + throw new RuntimeException("run failure"); + } + + @Override + protected Boolean getFallback() { + try { + Thread.sleep(500); + return false; + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + } + + HystrixCommand cmd = new AsyncCommand(); + + final CountDownLatch latch = new CountDownLatch(1); + + Observable o = cmd.toObservable(); + Subscription s = o. + doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println("OnUnsubscribe"); + latch.countDown(); + } + }). + subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println("OnError : " + e); + } + + @Override + public void onNext(Boolean b) { + System.out.println("OnNext : " + b); + } + }); + + try { + Thread.sleep(10); //give fallback a chance to fire + s.unsubscribe(); + assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); + assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount()); + assertFalse(cmd.isExecutionComplete()); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + @Test + public void testRequestThenCacheHitAndCacheHitUnsubscribed() { + AsyncCacheableCommand original = new AsyncCacheableCommand("foo"); + AsyncCacheableCommand fromCache = new AsyncCacheableCommand("foo"); + + final AtomicReference originalValue = new AtomicReference(null); + final AtomicReference fromCacheValue = new AtomicReference(null); + + final CountDownLatch originalLatch = new CountDownLatch(1); + final CountDownLatch fromCacheLatch = new CountDownLatch(1); + + Observable originalObservable = original.toObservable(); + Observable fromCacheObservable = fromCache.toObservable(); + + Subscription originalSubscription = originalObservable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original Unsubscribe"); + originalLatch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnCompleted"); + originalLatch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnError : " + e); + originalLatch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnNext : " + b); + originalValue.set(b); + } + }); + + Subscription fromCacheSubscription = fromCacheObservable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " FromCache Unsubscribe"); + fromCacheLatch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " FromCache OnCompleted"); + fromCacheLatch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " FromCache OnError : " + e); + fromCacheLatch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " FromCache OnNext : " + b); + fromCacheValue.set(b); + } + }); + + try { + fromCacheSubscription.unsubscribe(); + assertTrue(fromCacheLatch.await(600, TimeUnit.MILLISECONDS)); + assertTrue(originalLatch.await(600, TimeUnit.MILLISECONDS)); + assertEquals("Number of execution semaphores in use (original)", 0, original.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (original)", 0, original.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertTrue(original.isExecutionComplete()); + assertTrue(original.isExecutedInThread()); + assertEquals(null, original.getFailedExecutionException()); + assertNull(original.getExecutionException()); + assertTrue(original.getExecutionTimeInMilliseconds() > -1); + assertTrue(original.isSuccessfulExecution()); + assertCommandExecutionEvents(original, HystrixEventType.SUCCESS); + assertTrue(originalValue.get()); + assertEquals(0, original.metrics.getCurrentConcurrentExecutionCount()); + + + assertEquals("Number of execution semaphores in use (fromCache)", 0, fromCache.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (fromCache)", 0, fromCache.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(fromCache.isExecutionComplete()); + assertFalse(fromCache.isExecutedInThread()); + assertEquals(null, fromCache.getFailedExecutionException()); + assertNull(fromCache.getExecutionException()); + assertCommandExecutionEvents(fromCache, HystrixEventType.RESPONSE_FROM_CACHE, HystrixEventType.CANCELLED); + assertTrue(fromCache.getExecutionTimeInMilliseconds() == -1); + assertFalse(fromCache.isSuccessfulExecution()); + assertEquals(0, fromCache.metrics.getCurrentConcurrentExecutionCount()); + + assertFalse(original.isCancelled()); //underlying work + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertSaneHystrixRequestLog(2); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + @Test + public void testRequestThenCacheHitAndOriginalUnsubscribed() { + AsyncCacheableCommand original = new AsyncCacheableCommand("foo"); + AsyncCacheableCommand fromCache = new AsyncCacheableCommand("foo"); + + final AtomicReference originalValue = new AtomicReference(null); + final AtomicReference fromCacheValue = new AtomicReference(null); + + final CountDownLatch originalLatch = new CountDownLatch(1); + final CountDownLatch fromCacheLatch = new CountDownLatch(1); + + Observable originalObservable = original.toObservable(); + Observable fromCacheObservable = fromCache.toObservable(); + + Subscription originalSubscription = originalObservable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original Unsubscribe"); + originalLatch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnCompleted"); + originalLatch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnError : " + e); + originalLatch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnNext : " + b); + originalValue.set(b); + } + }); + + Subscription fromCacheSubscription = fromCacheObservable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache Unsubscribe"); + fromCacheLatch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache OnCompleted"); + fromCacheLatch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache OnError : " + e); + fromCacheLatch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache OnNext : " + b); + fromCacheValue.set(b); + } + }); + + try { + Thread.sleep(10); + originalSubscription.unsubscribe(); + assertTrue(originalLatch.await(600, TimeUnit.MILLISECONDS)); + assertTrue(fromCacheLatch.await(600, TimeUnit.MILLISECONDS)); + assertEquals("Number of execution semaphores in use (original)", 0, original.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (original)", 0, original.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(original.isExecutionComplete()); + assertTrue(original.isExecutedInThread()); + assertEquals(null, original.getFailedExecutionException()); + assertNull(original.getExecutionException()); + assertTrue(original.getExecutionTimeInMilliseconds() > -1); + assertFalse(original.isSuccessfulExecution()); + assertCommandExecutionEvents(original, HystrixEventType.CANCELLED); + assertNull(originalValue.get()); + assertEquals(0, original.metrics.getCurrentConcurrentExecutionCount()); + + assertEquals("Number of execution semaphores in use (fromCache)", 0, fromCache.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (fromCache)", 0, fromCache.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertTrue(fromCache.isExecutionComplete()); + assertFalse(fromCache.isExecutedInThread()); + assertEquals(null, fromCache.getFailedExecutionException()); + assertNull(fromCache.getExecutionException()); + assertCommandExecutionEvents(fromCache, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertTrue(fromCache.getExecutionTimeInMilliseconds() == -1); + assertTrue(fromCache.isSuccessfulExecution()); + assertEquals(0, fromCache.metrics.getCurrentConcurrentExecutionCount()); + + assertFalse(original.isCancelled()); //underlying work + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertSaneHystrixRequestLog(2); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + @Test + public void testRequestThenTwoCacheHitsOriginalAndOneCacheHitUnsubscribed() { + AsyncCacheableCommand original = new AsyncCacheableCommand("foo"); + AsyncCacheableCommand fromCache1 = new AsyncCacheableCommand("foo"); + AsyncCacheableCommand fromCache2 = new AsyncCacheableCommand("foo"); + + final AtomicReference originalValue = new AtomicReference(null); + final AtomicReference fromCache1Value = new AtomicReference(null); + final AtomicReference fromCache2Value = new AtomicReference(null); + + final CountDownLatch originalLatch = new CountDownLatch(1); + final CountDownLatch fromCache1Latch = new CountDownLatch(1); + final CountDownLatch fromCache2Latch = new CountDownLatch(1); + + Observable originalObservable = original.toObservable(); + Observable fromCache1Observable = fromCache1.toObservable(); + Observable fromCache2Observable = fromCache2.toObservable(); + + Subscription originalSubscription = originalObservable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original Unsubscribe"); + originalLatch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnCompleted"); + originalLatch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnError : " + e); + originalLatch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnNext : " + b); + originalValue.set(b); + } + }); + + Subscription fromCache1Subscription = fromCache1Observable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 Unsubscribe"); + fromCache1Latch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnCompleted"); + fromCache1Latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnError : " + e); + fromCache1Latch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnNext : " + b); + fromCache1Value.set(b); + } + }); + + Subscription fromCache2Subscription = fromCache2Observable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 Unsubscribe"); + fromCache2Latch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnCompleted"); + fromCache2Latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnError : " + e); + fromCache2Latch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnNext : " + b); + fromCache2Value.set(b); + } + }); + + try { + Thread.sleep(10); + originalSubscription.unsubscribe(); + //fromCache1Subscription.unsubscribe(); + fromCache2Subscription.unsubscribe(); + assertTrue(originalLatch.await(600, TimeUnit.MILLISECONDS)); + assertTrue(fromCache1Latch.await(600, TimeUnit.MILLISECONDS)); + assertTrue(fromCache2Latch.await(600, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + + assertEquals("Number of execution semaphores in use (original)", 0, original.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (original)", 0, original.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(original.isExecutionComplete()); + assertTrue(original.isExecutedInThread()); + assertEquals(null, original.getFailedExecutionException()); + assertNull(original.getExecutionException()); + assertTrue(original.getExecutionTimeInMilliseconds() > -1); + assertFalse(original.isSuccessfulExecution()); + assertCommandExecutionEvents(original, HystrixEventType.CANCELLED); + assertNull(originalValue.get()); + assertFalse(original.isCancelled()); //underlying work + assertEquals(0, original.metrics.getCurrentConcurrentExecutionCount()); + + + assertEquals("Number of execution semaphores in use (fromCache1)", 0, fromCache1.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (fromCache1)", 0, fromCache1.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertTrue(fromCache1.isExecutionComplete()); + assertFalse(fromCache1.isExecutedInThread()); + assertEquals(null, fromCache1.getFailedExecutionException()); + assertNull(fromCache1.getExecutionException()); + assertCommandExecutionEvents(fromCache1, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertTrue(fromCache1.getExecutionTimeInMilliseconds() == -1); + assertTrue(fromCache1.isSuccessfulExecution()); + assertTrue(fromCache1Value.get()); + assertEquals(0, fromCache1.metrics.getCurrentConcurrentExecutionCount()); + + + assertEquals("Number of execution semaphores in use (fromCache2)", 0, fromCache2.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (fromCache2)", 0, fromCache2.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(fromCache2.isExecutionComplete()); + assertFalse(fromCache2.isExecutedInThread()); + assertEquals(null, fromCache2.getFailedExecutionException()); + assertNull(fromCache2.getExecutionException()); + assertCommandExecutionEvents(fromCache2, HystrixEventType.RESPONSE_FROM_CACHE, HystrixEventType.CANCELLED); + assertTrue(fromCache2.getExecutionTimeInMilliseconds() == -1); + assertFalse(fromCache2.isSuccessfulExecution()); + assertNull(fromCache2Value.get()); + assertEquals(0, fromCache2.metrics.getCurrentConcurrentExecutionCount()); + + assertSaneHystrixRequestLog(3); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + @Test + public void testRequestThenTwoCacheHitsAllUnsubscribed() { + AsyncCacheableCommand original = new AsyncCacheableCommand("foo"); + AsyncCacheableCommand fromCache1 = new AsyncCacheableCommand("foo"); + AsyncCacheableCommand fromCache2 = new AsyncCacheableCommand("foo"); + + final CountDownLatch originalLatch = new CountDownLatch(1); + final CountDownLatch fromCache1Latch = new CountDownLatch(1); + final CountDownLatch fromCache2Latch = new CountDownLatch(1); + + Observable originalObservable = original.toObservable(); + Observable fromCache1Observable = fromCache1.toObservable(); + Observable fromCache2Observable = fromCache2.toObservable(); + + Subscription originalSubscription = originalObservable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original Unsubscribe"); + originalLatch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnCompleted"); + originalLatch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnError : " + e); + originalLatch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnNext : " + b); + } + }); + + Subscription fromCache1Subscription = fromCache1Observable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 Unsubscribe"); + fromCache1Latch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnCompleted"); + fromCache1Latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnError : " + e); + fromCache1Latch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnNext : " + b); + } + }); + + Subscription fromCache2Subscription = fromCache2Observable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 Unsubscribe"); + fromCache2Latch.countDown(); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnCompleted"); + fromCache2Latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnError : " + e); + fromCache2Latch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnNext : " + b); + } + }); + + try { + Thread.sleep(10); + originalSubscription.unsubscribe(); + fromCache1Subscription.unsubscribe(); + fromCache2Subscription.unsubscribe(); + assertTrue(originalLatch.await(200, TimeUnit.MILLISECONDS)); + assertTrue(fromCache1Latch.await(200, TimeUnit.MILLISECONDS)); + assertTrue(fromCache2Latch.await(200, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + + assertEquals("Number of execution semaphores in use (original)", 0, original.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (original)", 0, original.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(original.isExecutionComplete()); + assertTrue(original.isExecutedInThread()); + System.out.println("FEE : " + original.getFailedExecutionException()); + if (original.getFailedExecutionException() != null) { + original.getFailedExecutionException().printStackTrace(); + } + assertNull(original.getFailedExecutionException()); + assertNull(original.getExecutionException()); + assertTrue(original.getExecutionTimeInMilliseconds() > -1); + assertFalse(original.isSuccessfulExecution()); + assertCommandExecutionEvents(original, HystrixEventType.CANCELLED); + //assertTrue(original.isCancelled()); //underlying work This doesn't work yet + assertEquals(0, original.metrics.getCurrentConcurrentExecutionCount()); + + + assertEquals("Number of execution semaphores in use (fromCache1)", 0, fromCache1.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (fromCache1)", 0, fromCache1.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(fromCache1.isExecutionComplete()); + assertFalse(fromCache1.isExecutedInThread()); + assertEquals(null, fromCache1.getFailedExecutionException()); + assertNull(fromCache1.getExecutionException()); + assertCommandExecutionEvents(fromCache1, HystrixEventType.RESPONSE_FROM_CACHE, HystrixEventType.CANCELLED); + assertTrue(fromCache1.getExecutionTimeInMilliseconds() == -1); + assertFalse(fromCache1.isSuccessfulExecution()); + assertEquals(0, fromCache1.metrics.getCurrentConcurrentExecutionCount()); + + + assertEquals("Number of execution semaphores in use (fromCache2)", 0, fromCache2.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use (fromCache2)", 0, fromCache2.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(fromCache2.isExecutionComplete()); + assertFalse(fromCache2.isExecutedInThread()); + assertEquals(null, fromCache2.getFailedExecutionException()); + assertNull(fromCache2.getExecutionException()); + assertCommandExecutionEvents(fromCache2, HystrixEventType.RESPONSE_FROM_CACHE, HystrixEventType.CANCELLED); + assertTrue(fromCache2.getExecutionTimeInMilliseconds() == -1); + assertFalse(fromCache2.isSuccessfulExecution()); + assertEquals(0, fromCache2.metrics.getCurrentConcurrentExecutionCount()); + + assertSaneHystrixRequestLog(3); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + /** + * Some RxJava operators like take(n), zip receive data in an onNext from upstream and immediately unsubscribe. + * When upstream is a HystrixCommand, Hystrix may get that unsubscribe before it gets to its onCompleted. + * This should still be marked as a HystrixEventType.SUCCESS. + */ + @Test + public void testUnsubscribingDownstreamOperatorStillResultsInSuccessEventType() throws InterruptedException { + HystrixCommand cmd = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 100, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + + Observable o = cmd.toObservable() + .doOnNext(new Action1() { + @Override + public void call(Integer i) { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnNext : " + i); + } + }) + .doOnError(new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnError : " + throwable); + } + }) + .doOnCompleted(new Action0() { + @Override + public void call() { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnCompleted"); + } + }) + .doOnSubscribe(new Action0() { + @Override + public void call() { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnSubscribe"); + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnUnsubscribe"); + } + }) + .take(1) + .observeOn(Schedulers.io()) + .map(new Func1() { + @Override + public Integer call(Integer i) { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : Doing some more computation in the onNext!!"); + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + return i; + } + }); + + final CountDownLatch latch = new CountDownLatch(1); + + o.doOnSubscribe(new Action0() { + @Override + public void call() { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnSubscribe"); + } + }).doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnUnsubscribe"); + } + }).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnError : " + e); + latch.countDown(); + } + + @Override + public void onNext(Integer i) { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnNext : " + i); + } + }); + + latch.await(1000, TimeUnit.MILLISECONDS); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(cmd.isExecutedInThread()); + assertCommandExecutionEvents(cmd, HystrixEventType.SUCCESS); + } + + @Test + public void testUnsubscribeBeforeSubscribe() throws Exception { + //this may happen in Observable chain, so Hystrix should make sure that command never executes/allocates in this situation + Observable error = Observable.error(new RuntimeException("foo")); + HystrixCommand cmd = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 100); + Observable cmdResult = cmd.toObservable() + .doOnNext(new Action1() { + @Override + public void call(Integer integer) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnNext : " + integer); + } + }) + .doOnError(new Action1() { + @Override + public void call(Throwable ex) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnError : " + ex); + } + }) + .doOnCompleted(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnCompleted"); + } + }) + .doOnSubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnSubscribe"); + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnUnsubscribe"); + } + }); + + //the zip operator will subscribe to each observable. there is a race between the error of the first + //zipped observable terminating the zip and the subscription to the command's observable + Observable zipped = Observable.zip(error, cmdResult, new Func2() { + @Override + public String call(String s, Integer integer) { + return s + integer; + } + }); + + final CountDownLatch latch = new CountDownLatch(1); + + zipped.subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnError : " + e); + latch.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnNext : " + s); + } + }); + + latch.await(1000, TimeUnit.MILLISECONDS); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + } + + @Test + public void testRxRetry() throws Exception { + // see https://github.com/Netflix/Hystrix/issues/1100 + // Since each command instance is single-use, the expectation is that applying the .retry() operator + // results in only a single execution and propagation out of that error + HystrixCommand cmd = getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 300, + AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 100); + + final CountDownLatch latch = new CountDownLatch(1); + + System.out.println(System.currentTimeMillis() + " : Starting"); + Observable o = cmd.toObservable().retry(2); + System.out.println(System.currentTimeMillis() + " Created retried command : " + o); + + o.subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnError : " + e); + latch.countDown(); + } + + @Override + public void onNext(Integer integer) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnNext : " + integer); + } + }); + + latch.await(1000, TimeUnit.MILLISECONDS); + System.out.println(System.currentTimeMillis() + " ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + } + + /** + *********************** THREAD-ISOLATED Execution Hook Tests ************************************** + */ + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: SUCCESS + */ + @Test + public void testExecutionHookThreadSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(1, 0, 1)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionSuccess - onThreadComplete - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + @Test + public void testExecutionHookEarlyUnsubscribe() { + System.out.println("Running command.observe(), awaiting terminal state of Observable, then running assertions..."); + final CountDownLatch latch = new CountDownLatch(1); + + TestHystrixCommand command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 1000); + Observable o = command.observe(); + + Subscription s = o. + doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnUnsubscribe"); + latch.countDown(); + } + }). + subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnError : " + e); + latch.countDown(); + } + + @Override + public void onNext(Integer i) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnNext : " + i); + } + }); + + try { + Thread.sleep(15); + s.unsubscribe(); + latch.await(3, TimeUnit.SECONDS); + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 0, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onUnsubscribe - onThreadComplete - ", hook.executionSequence.toString()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: synchronous HystrixBadRequestException + */ + @Test + public void testExecutionHookThreadBadRequestException() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(HystrixBadRequestException.class, hook.getCommandException().getClass()); + assertEquals(HystrixBadRequestException.class, hook.getExecutionException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onError - ", hook.executionSequence.toString()); + } + }); + } + + + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: synchronous HystrixRuntimeException + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadExceptionNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 0, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: synchronous HystrixRuntimeException + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadExceptionSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 0, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: synchronous HystrixRuntimeException + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadExceptionUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 0, AbstractTestHystrixCommand.FallbackResult.FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadTimeoutNoFallbackRunSuccess() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 200); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(TimeoutException.class, hook.getCommandException().getClass()); + assertNull(hook.getFallbackException()); + System.out.println("RequestLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadTimeoutSuccessfulFallbackRunSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 200); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + System.out.println("RequestLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: synchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunSuccess() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, 200); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(TimeoutException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadTimeoutNoFallbackRunFailure() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 200); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(TimeoutException.class, hook.getCommandException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onError - ", hook.executionSequence.toString()); + + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadTimeoutSuccessfulFallbackRunFailure() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 200); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: synchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunFailure() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, 200); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(TimeoutException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadPoolQueueFullNoFallback() { + assertHooksOnFailFast( + new Func0>() { + @Override + public TestHystrixCommand call() { + HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600).observe(); + // fill the queue + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600).observe(); + } catch (Exception e) { + // ignore + } + return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(RejectedExecutionException.class, hook.getCommandException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadPoolQueueFullSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600).observe(); + // fill the queue + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600).observe(); + } catch (Exception e) { + // ignore + } + + return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + assertEquals("onStart - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: synchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadPoolQueueFullUnsuccessfulFallback() { + assertHooksOnFailFast( + new Func0>() { + @Override + public TestHystrixCommand call() { + HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600).observe(); + // fill the queue + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600).observe(); + } catch (Exception e) { + // ignore + } + + return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RejectedExecutionException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadPoolFullNoFallback() { + assertHooksOnFailFast( + new Func0>() { + @Override + public TestHystrixCommand call() { + HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600).observe(); + } catch (Exception e) { + // ignore + } + + return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(RejectedExecutionException.class, hook.getCommandException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadPoolFullSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600).observe(); + } catch (Exception e) { + // ignore + } + + return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertEquals("onStart - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: synchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadPoolFullUnsuccessfulFallback() { + assertHooksOnFailFast( + new Func0>() { + @Override + public TestHystrixCommand call() { + HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600).observe(); + } catch (Exception e) { + // ignore + } + + return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RejectedExecutionException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadShortCircuitNoFallback() { + assertHooksOnFailFast( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCircuitOpenCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadShortCircuitSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCircuitOpenCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + assertEquals("onStart - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: synchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadShortCircuitUnsuccessfulFallback() { + assertHooksOnFailFast( + new Func0>() { + @Override + public TestHystrixCommand call() { + HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker().setForceShortCircuit(true); + return getCircuitOpenCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Request-cache? : YES + */ + @Test + public void testExecutionHookResponseFromCache() { + final HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Hook-Cache"); + getCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 0, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, 100, AbstractTestHystrixCommand.CacheEnabled.YES, 42, 10, 10).observe(); + + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return getCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 0, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, 100, AbstractTestHystrixCommand.CacheEnabled.YES, 42, 10, 10); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 0, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals("onCacheHit - ", hook.executionSequence.toString()); + } + }); + } + + /** + *********************** END THREAD-ISOLATED Execution Hook Tests ************************************** + */ + + + /* ******************************************************************************** */ + /* ******************************************************************************** */ + /* private HystrixCommand class implementations for unit testing */ + /* ******************************************************************************** */ + /* ******************************************************************************** */ + + static AtomicInteger uniqueNameCounter = new AtomicInteger(1); + + @Override + TestHystrixCommand getCommand(ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("Flexible-" + uniqueNameCounter.getAndIncrement()); + return FlexibleTestHystrixCommand.from(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + + @Override + TestHystrixCommand getCommand(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + return FlexibleTestHystrixCommand.from(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + + private static class FlexibleTestHystrixCommand { + + public static Integer EXECUTE_VALUE = 1; + public static Integer FALLBACK_VALUE = 11; + + public static AbstractFlexibleTestHystrixCommand from(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + if (fallbackResult.equals(AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED)) { + return new FlexibleTestHystrixCommandNoFallback(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } else { + return new FlexibleTestHystrixCommandWithFallback(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + } + } + + private static class AbstractFlexibleTestHystrixCommand extends TestHystrixCommand { + protected final AbstractTestHystrixCommand.ExecutionResult executionResult; + protected final int executionLatency; + + protected final CacheEnabled cacheEnabled; + protected final Object value; + + + AbstractFlexibleTestHystrixCommand(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + super(testPropsBuilder(circuitBreaker) + .setCommandKey(commandKey) + .setCircuitBreaker(circuitBreaker) + .setMetrics(circuitBreaker.metrics) + .setThreadPool(threadPool) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(isolationStrategy) + .withExecutionTimeoutInMilliseconds(timeout) + .withCircuitBreakerEnabled(!circuitBreakerDisabled)) + .setExecutionSemaphore(executionSemaphore) + .setFallbackSemaphore(fallbackSemaphore)); + this.executionResult = executionResult; + this.executionLatency = executionLatency; + + this.cacheEnabled = cacheEnabled; + this.value = value; + } + + @Override + protected Integer run() throws Exception { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " starting the run() method"); + addLatency(executionLatency); + if (executionResult == AbstractTestHystrixCommand.ExecutionResult.SUCCESS) { + return FlexibleTestHystrixCommand.EXECUTE_VALUE; + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.FAILURE) { + throw new RuntimeException("Execution Failure for TestHystrixCommand"); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE) { + throw new NotWrappedByHystrixTestRuntimeException(); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE) { + throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, AbstractFlexibleTestHystrixCommand.class, "Execution Hystrix Failure for TestHystrixCommand", new RuntimeException("Execution Failure for TestHystrixCommand"), new RuntimeException("Fallback Failure for TestHystrixCommand")); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.RECOVERABLE_ERROR) { + throw new java.lang.Error("Execution ERROR for TestHystrixCommand"); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.UNRECOVERABLE_ERROR) { + throw new StackOverflowError("Unrecoverable Error for TestHystrixCommand"); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST) { + throw new HystrixBadRequestException("Execution BadRequestException for TestHystrixCommand"); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST_NOT_WRAPPED) { + throw new HystrixBadRequestException("Execution BadRequestException for TestHystrixCommand", new NotWrappedByHystrixTestRuntimeException()); + } else { + throw new RuntimeException("You passed in a executionResult enum that can't be represented in HystrixCommand: " + executionResult); + } + } + + @Override + public String getCacheKey() { + if (cacheEnabled == CacheEnabled.YES) + return value.toString(); + else + return null; + } + + protected void addLatency(int latency) { + if (latency > 0) { + try { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " About to sleep for : " + latency); + Thread.sleep(latency); + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Woke up from sleep!"); + } catch (InterruptedException e) { + e.printStackTrace(); + // ignore and sleep some more to simulate a dependency that doesn't obey interrupts + try { + Thread.sleep(latency); + } catch (Exception e2) { + // ignore + } + System.out.println("after interruption with extra sleep"); + } + } + } + + } + + private static class FlexibleTestHystrixCommandWithFallback extends AbstractFlexibleTestHystrixCommand { + protected final AbstractTestHystrixCommand.FallbackResult fallbackResult; + protected final int fallbackLatency; + + FlexibleTestHystrixCommandWithFallback(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + super(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + this.fallbackResult = fallbackResult; + this.fallbackLatency = fallbackLatency; + } + + @Override + protected Integer getFallback() { + addLatency(fallbackLatency); + if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.SUCCESS) { + return FlexibleTestHystrixCommand.FALLBACK_VALUE; + } else if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.FAILURE) { + throw new RuntimeException("Fallback Failure for TestHystrixCommand"); + } else if (fallbackResult == FallbackResult.UNIMPLEMENTED) { + return super.getFallback(); + } else { + throw new RuntimeException("You passed in a fallbackResult enum that can't be represented in HystrixCommand: " + fallbackResult); + } + } + } + + private static class FlexibleTestHystrixCommandNoFallback extends AbstractFlexibleTestHystrixCommand { + FlexibleTestHystrixCommandNoFallback(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + super(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + } + + /** + * Successful execution - no fallback implementation. + */ + private static class SuccessfulTestCommand extends TestHystrixCommand { + + public SuccessfulTestCommand() { + this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter()); + } + + public SuccessfulTestCommand(HystrixCommandProperties.Setter properties) { + super(testPropsBuilder().setCommandPropertiesDefaults(properties)); + } + + @Override + protected Boolean run() { + return true; + } + + } + + /** + * Successful execution - no fallback implementation. + */ + private static class DynamicOwnerTestCommand extends TestHystrixCommand { + + public DynamicOwnerTestCommand(HystrixCommandGroupKey owner) { + super(testPropsBuilder().setOwner(owner)); + } + + @Override + protected Boolean run() { + System.out.println("successfully executed"); + return true; + } + + } + + /** + * Successful execution - no fallback implementation. + */ + private static class DynamicOwnerAndKeyTestCommand extends TestHystrixCommand { + + public DynamicOwnerAndKeyTestCommand(HystrixCommandGroupKey owner, HystrixCommandKey key) { + super(testPropsBuilder().setOwner(owner).setCommandKey(key).setCircuitBreaker(null).setMetrics(null)); + // we specifically are NOT passing in a circuit breaker here so we test that it creates a new one correctly based on the dynamic key + } + + @Override + protected Boolean run() { + System.out.println("successfully executed"); + return true; + } + + } + + /** + * Failed execution with known exception (HystrixException) - no fallback implementation. + */ + private static class KnownFailureTestCommandWithoutFallback extends TestHystrixCommand { + + private KnownFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Boolean run() { + System.out.println("*** simulated failed execution *** ==> " + Thread.currentThread()); + throw new RuntimeException("we failed with a simulated issue"); + } + + } + + /** + * Failed execution - fallback implementation successfully returns value. + */ + private static class KnownFailureTestCommandWithFallback extends TestHystrixCommand { + + public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, boolean fallbackEnabled) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withFallbackEnabled(fallbackEnabled))); + } + + @Override + protected Boolean run() { + System.out.println("*** simulated failed execution ***"); + throw new RuntimeException("we failed with a simulated issue"); + } + + @Override + protected Boolean getFallback() { + return false; + } + } + + /** + * A Command implementation that supports caching. + */ + private static class SuccessfulCacheableCommand extends TestHystrixCommand { + + private final boolean cacheEnabled; + private volatile boolean executed = false; + private final T value; + + public SuccessfulCacheableCommand(TestCircuitBreaker circuitBreaker, boolean cacheEnabled, T value) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + this.value = value; + this.cacheEnabled = cacheEnabled; + } + + @Override + protected T run() { + executed = true; + System.out.println("successfully executed"); + return value; + } + + public boolean isCommandRunningInThread() { + return super.getProperties().executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD); + } + + @Override + public String getCacheKey() { + if (cacheEnabled) + return value.toString(); + else + return null; + } + } + + /** + * A Command implementation that supports caching. + */ + private static class SuccessfulCacheableCommandViaSemaphore extends TestHystrixCommand { + + private final boolean cacheEnabled; + private volatile boolean executed = false; + private final String value; + + public SuccessfulCacheableCommandViaSemaphore(TestCircuitBreaker circuitBreaker, boolean cacheEnabled, String value) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); + this.value = value; + this.cacheEnabled = cacheEnabled; + } + + @Override + protected String run() { + executed = true; + System.out.println("successfully executed"); + return value; + } + + public boolean isCommandRunningInThread() { + return super.getProperties().executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD); + } + + @Override + public String getCacheKey() { + if (cacheEnabled) + return value; + else + return null; + } + } + + /** + * A Command implementation that supports caching and execution takes a while. + *

+ * Used to test scenario where Futures are returned with a backing call still executing. + */ + private static class SlowCacheableCommand extends TestHystrixCommand { + + private final String value; + private final int duration; + private volatile boolean executed = false; + + public SlowCacheableCommand(TestCircuitBreaker circuitBreaker, String value, int duration) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + this.value = value; + this.duration = duration; + } + + @Override + protected String run() { + executed = true; + try { + Thread.sleep(duration); + } catch (Exception e) { + e.printStackTrace(); + } + System.out.println("successfully executed"); + return value; + } + + @Override + public String getCacheKey() { + return value; + } + } + + /** + * This has a ThreadPool that has a single thread and queueSize of 1. + */ + private static class TestCommandRejection extends TestHystrixCommand { + + private final static int FALLBACK_NOT_IMPLEMENTED = 1; + private final static int FALLBACK_SUCCESS = 2; + private final static int FALLBACK_FAILURE = 3; + + private final int fallbackBehavior; + + private final int sleepTime; + + private TestCommandRejection(HystrixCommandKey key, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int sleepTime, int timeout, int fallbackBehavior) { + super(testPropsBuilder() + .setCommandKey(key) + .setThreadPool(threadPool) + .setCircuitBreaker(circuitBreaker) + .setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(timeout))); + this.fallbackBehavior = fallbackBehavior; + this.sleepTime = sleepTime; + } + + @Override + protected Boolean run() { + System.out.println(">>> TestCommandRejection running"); + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + } + + @Override + protected Boolean getFallback() { + if (fallbackBehavior == FALLBACK_SUCCESS) { + return false; + } else if (fallbackBehavior == FALLBACK_FAILURE) { + throw new RuntimeException("failed on fallback"); + } else { + // FALLBACK_NOT_IMPLEMENTED + return super.getFallback(); + } + } + } + + /** + * Command that receives a custom thread-pool, sleepTime, timeout + */ + private static class CommandWithCustomThreadPool extends TestHystrixCommand { + + public boolean didExecute = false; + + private final int sleepTime; + + private CommandWithCustomThreadPool(TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int sleepTime, HystrixCommandProperties.Setter properties) { + super(testPropsBuilder().setThreadPool(threadPool).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics).setCommandPropertiesDefaults(properties)); + this.sleepTime = sleepTime; + } + + @Override + protected Boolean run() { + System.out.println("**** Executing CommandWithCustomThreadPool. Execution => " + sleepTime); + didExecute = true; + try { + Thread.sleep(sleepTime); + System.out.println("Woke up"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + } + } + + /** + * The run() will fail and getFallback() take a long time. + */ + private static class TestSemaphoreCommandWithSlowFallback extends TestHystrixCommand { + + private final long fallbackSleep; + + private TestSemaphoreCommandWithSlowFallback(TestCircuitBreaker circuitBreaker, int fallbackSemaphoreExecutionCount, long fallbackSleep) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withFallbackIsolationSemaphoreMaxConcurrentRequests(fallbackSemaphoreExecutionCount).withExecutionIsolationThreadInterruptOnTimeout(false))); + this.fallbackSleep = fallbackSleep; + } + + @Override + protected Boolean run() { + throw new RuntimeException("run fails"); + } + + @Override + protected Boolean getFallback() { + try { + Thread.sleep(fallbackSleep); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + } + } + + private static class NoRequestCacheTimeoutWithoutFallback extends TestHystrixCommand { + public NoRequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(200).withCircuitBreakerEnabled(false))); + + // we want it to timeout + } + + @Override + protected Boolean run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + System.out.println(">>>> Sleep Interrupted: " + e.getMessage()); + // e.printStackTrace(); + } + return true; + } + + @Override + public String getCacheKey() { + return null; + } + } + + /** + * The run() will take time. Configurable fallback implementation. + */ + private static class TestSemaphoreCommand extends TestHystrixCommand { + + private final long executionSleep; + + private final static int RESULT_SUCCESS = 1; + private final static int RESULT_FAILURE = 2; + private final static int RESULT_BAD_REQUEST_EXCEPTION = 3; + + private final int resultBehavior; + + private final static int FALLBACK_SUCCESS = 10; + private final static int FALLBACK_NOT_IMPLEMENTED = 11; + private final static int FALLBACK_FAILURE = 12; + + private final int fallbackBehavior; + + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep, int resultBehavior, int fallbackBehavior) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) + .withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); + this.executionSleep = executionSleep; + this.resultBehavior = resultBehavior; + this.fallbackBehavior = fallbackBehavior; + } + + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, long executionSleep, int resultBehavior, int fallbackBehavior) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)) + .setExecutionSemaphore(semaphore)); + this.executionSleep = executionSleep; + this.resultBehavior = resultBehavior; + this.fallbackBehavior = fallbackBehavior; + } + + @Override + protected Boolean run() { + try { + Thread.sleep(executionSleep); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (resultBehavior == RESULT_SUCCESS) { + return true; + } else if (resultBehavior == RESULT_FAILURE) { + throw new RuntimeException("TestSemaphoreCommand failure"); + } else if (resultBehavior == RESULT_BAD_REQUEST_EXCEPTION) { + throw new HystrixBadRequestException("TestSemaphoreCommand BadRequestException"); + } else { + throw new IllegalStateException("Didn't use a proper enum for result behavior"); + } + } + + + @Override + protected Boolean getFallback() { + if (fallbackBehavior == FALLBACK_SUCCESS) { + return false; + } else if (fallbackBehavior == FALLBACK_FAILURE) { + throw new RuntimeException("fallback failure"); + } else { //FALLBACK_NOT_IMPLEMENTED + return super.getFallback(); + } + } + } + + /** + * Semaphore based command that allows caller to use latches to know when it has started and signal when it + * would like the command to finish + */ + private static class LatchedSemaphoreCommand extends TestHystrixCommand { + + private final CountDownLatch startLatch, waitLatch; + + /** + * + * @param circuitBreaker circuit breaker (passed in so it may be shared) + * @param semaphore semaphore (passed in so it may be shared) + * @param startLatch + * this command calls {@link java.util.concurrent.CountDownLatch#countDown()} immediately + * upon running + * @param waitLatch + * this command calls {@link java.util.concurrent.CountDownLatch#await()} once it starts + * to run. The caller can use the latch to signal the command to finish + */ + private LatchedSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, CountDownLatch startLatch, CountDownLatch waitLatch) { + this("Latched", circuitBreaker, semaphore, startLatch, waitLatch); + } + + private LatchedSemaphoreCommand(String commandName, TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, + CountDownLatch startLatch, CountDownLatch waitLatch) { + super(testPropsBuilder() + .setCommandKey(HystrixCommandKey.Factory.asKey(commandName)) + .setCircuitBreaker(circuitBreaker) + .setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) + .withCircuitBreakerEnabled(false)) + .setExecutionSemaphore(semaphore)); + this.startLatch = startLatch; + this.waitLatch = waitLatch; + } + + @Override + protected Boolean run() { + // signals caller that run has started + this.startLatch.countDown(); + + try { + // waits for caller to countDown latch + this.waitLatch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } + return true; + } + } + + /** + * The run() will take time. Contains fallback. + */ + private static class TestSemaphoreCommandWithFallback extends TestHystrixCommand { + + private final long executionSleep; + private final Boolean fallback; + + private TestSemaphoreCommandWithFallback(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep, Boolean fallback) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); + this.executionSleep = executionSleep; + this.fallback = fallback; + } + + @Override + protected Boolean run() { + try { + Thread.sleep(executionSleep); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + } + + @Override + protected Boolean getFallback() { + return fallback; + } + + } + + private static class RequestCacheNullPointerExceptionCase extends TestHystrixCommand { + public RequestCacheNullPointerExceptionCase(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(200))); + // we want it to timeout + } + + @Override + protected Boolean run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + } + + @Override + protected Boolean getFallback() { + return false; + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class RequestCacheTimeoutWithoutFallback extends TestHystrixCommand { + public RequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(200).withCircuitBreakerEnabled(false))); + // we want it to timeout + } + + @Override + protected Boolean run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + System.out.println(">>>> Sleep Interrupted: " + e.getMessage()); + // e.printStackTrace(); + } + return true; + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class RequestCacheThreadRejectionWithoutFallback extends TestHystrixCommand { + + final CountDownLatch completionLatch; + + public RequestCacheThreadRejectionWithoutFallback(TestCircuitBreaker circuitBreaker, CountDownLatch completionLatch) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker) + .setMetrics(circuitBreaker.metrics) + .setThreadPool(new HystrixThreadPool() { + + @Override + public ThreadPoolExecutor getExecutor() { + return null; + } + + @Override + public void markThreadExecution() { + + } + + @Override + public void markThreadCompletion() { + + } + + @Override + public void markThreadRejection() { + + } + + @Override + public boolean isQueueSpaceAvailable() { + // always return false so we reject everything + return false; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + @Override + public Scheduler getScheduler(Func0 shouldInterruptThread) { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread); + } + + })); + this.completionLatch = completionLatch; + } + + @Override + protected Boolean run() { + try { + if (completionLatch.await(1000, TimeUnit.MILLISECONDS)) { + throw new RuntimeException("timed out waiting on completionLatch"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return true; + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class BadRequestCommand extends TestHystrixCommand { + + public BadRequestCommand(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationType) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationType)) + .setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Boolean run() { + throw new HystrixBadRequestException("Message to developer that they passed in bad data or something like that."); + } + + @Override + protected Boolean getFallback() { + return false; + } + + @Override + protected String getCacheKey() { + return "one"; + } + + } + + private static class AsyncCacheableCommand extends HystrixCommand { + private final String arg; + private final AtomicBoolean cancelled = new AtomicBoolean(false); + + public AsyncCacheableCommand(String arg) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); + this.arg = arg; + } + + @Override + protected Boolean run() { + try { + Thread.sleep(500); + return true; + } catch (InterruptedException ex) { + cancelled.set(true); + throw new RuntimeException(ex); + } + } + + @Override + protected String getCacheKey() { + return arg; + } + + public boolean isCancelled() { + return cancelled.get(); + } + } + + private static class BusinessException extends Exception { + public BusinessException(String msg) { + super(msg); + } + } + + private static class ExceptionToBadRequestByExecutionHookCommand extends TestHystrixCommand { + public ExceptionToBadRequestByExecutionHookCommand(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationType) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationType)) + .setMetrics(circuitBreaker.metrics) + .setExecutionHook(new TestableExecutionHook(){ + @Override + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { + super.onRunError(commandInstance, e); + return new HystrixBadRequestException("autoconverted exception", e); + } + })); + } + + @Override + protected Boolean run() throws BusinessException { + throw new BusinessException("invalid input by the user"); + } + + @Override + protected String getCacheKey() { + return "nein"; + } + } + + private static class CommandWithCheckedException extends TestHystrixCommand { + + public CommandWithCheckedException(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Boolean run() throws Exception { + throw new IOException("simulated checked exception message"); + } + + } + + private static class CommandWithNotWrappedByHystrixException extends TestHystrixCommand { + + public CommandWithNotWrappedByHystrixException(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Boolean run() throws Exception { + throw new NotWrappedByHystrixTestException(); + } + + } + + private static class InterruptibleCommand extends TestHystrixCommand { + + public InterruptibleCommand(TestCircuitBreaker circuitBreaker, boolean shouldInterrupt, boolean shouldInterruptOnCancel, int timeoutInMillis) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationThreadInterruptOnFutureCancel(shouldInterruptOnCancel) + .withExecutionIsolationThreadInterruptOnTimeout(shouldInterrupt) + .withExecutionTimeoutInMilliseconds(timeoutInMillis))); + } + + public InterruptibleCommand(TestCircuitBreaker circuitBreaker, boolean shouldInterrupt) { + this(circuitBreaker, shouldInterrupt, false, 100); + } + + private volatile boolean hasBeenInterrupted; + + public boolean hasBeenInterrupted() { + return hasBeenInterrupted; + } + + @Override + protected Boolean run() throws Exception { + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + System.out.println("Interrupted!"); + e.printStackTrace(); + hasBeenInterrupted = true; + } + + return hasBeenInterrupted; + } + } + + private static class CommandWithDisabledTimeout extends TestHystrixCommand { + private final int latency; + + public CommandWithDisabledTimeout(int timeout, int latency) { + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionTimeoutInMilliseconds(timeout) + .withExecutionTimeoutEnabled(false))); + this.latency = latency; + } + + @Override + protected Boolean run() throws Exception { + try { + Thread.sleep(latency); + return true; + } catch (InterruptedException ex) { + return false; + } + } + + @Override + protected Boolean getFallback() { + return false; + } + } + + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTestWithCustomConcurrencyStrategy.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTestWithCustomConcurrencyStrategy.java new file mode 100644 index 0000000..093b857 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTestWithCustomConcurrencyStrategy.java @@ -0,0 +1,328 @@ +/** + * 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 com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.Callable; + +import static org.junit.Assert.*; + +public class HystrixCommandTestWithCustomConcurrencyStrategy { + + @Before + public void init() { + HystrixPlugins.reset(); + } + + @After + public void reset() { + HystrixRequestContext.setContextOnCurrentThread(null); + HystrixPropertiesFactory.reset(); + HystrixPlugins.reset(); + } + + /** + * HystrixConcurrencyStrategy + ** useDefaultRequestContext : true + * HystrixCommand + ** useRequestCache : true + ** useRequestLog : true + * + * OUTCOME: RequestLog set up properly in command + */ + @Test + public void testCommandRequiresContextConcurrencyStrategyProvidesItContextSetUpCorrectly() { + HystrixConcurrencyStrategy strategy = new CustomConcurrencyStrategy(true); + HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); + + //context is set up properly + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + HystrixCommand cmd = new TestCommand(true, true); + assertTrue(cmd.execute()); + printRequestLog(); + assertNotNull(HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertNotNull(cmd.currentRequestLog); + context.shutdown(); + } + + /** + * HystrixConcurrencyStrategy + ** useDefaultRequestContext : true + * HystrixCommand + ** useRequestCache : true + ** useRequestLog : true + * + * OUTCOME: RequestLog not set up properly in command, static access is null + */ + @Test + public void testCommandRequiresContextConcurrencyStrategyProvidesItContextLeftUninitialized() { + HystrixConcurrencyStrategy strategy = new CustomConcurrencyStrategy(true); + HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); + + //context is not set up + HystrixRequestContext.setContextOnCurrentThread(null); + HystrixCommand cmd = new TestCommand(true, true); + assertTrue(cmd.execute()); //command execution not affected by missing context + printRequestLog(); + assertNull(HystrixRequestLog.getCurrentRequest()); + assertNull(HystrixRequestLog.getCurrentRequest(strategy)); + assertNull(cmd.currentRequestLog); + } + + /** + * HystrixConcurrencyStrategy + ** useDefaultRequestContext : false + * HystrixCommand + ** useRequestCache : true + ** useRequestLog : true + * + * OUTCOME: RequestLog not set up in command, not available statically + */ + @Test + public void testCommandRequiresContextConcurrencyStrategyDoesNotProvideItContextSetUpCorrectly() { + HystrixConcurrencyStrategy strategy = new CustomConcurrencyStrategy(false); + HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); + + //context is set up properly + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + HystrixCommand cmd = new TestCommand(true, true); + assertTrue(cmd.execute()); + printRequestLog(); + assertNull(HystrixRequestLog.getCurrentRequest()); + assertNull(HystrixRequestLog.getCurrentRequest(strategy)); + assertNull(cmd.currentRequestLog); + context.shutdown(); + } + + /** + * HystrixConcurrencyStrategy + ** useDefaultRequestContext : false + * HystrixCommand + ** useRequestCache : true + ** useRequestLog : true + * + * OUTCOME: RequestLog not set up in command, not available statically + */ + @Test + public void testCommandRequiresContextConcurrencyStrategyDoesNotProvideItContextLeftUninitialized() { + HystrixConcurrencyStrategy strategy = new CustomConcurrencyStrategy(false); + HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); + + //context is not set up + HystrixRequestContext.setContextOnCurrentThread(null); + HystrixCommand cmd = new TestCommand(true, true); + assertTrue(cmd.execute()); //command execution not affected by missing context + printRequestLog(); + assertNull(HystrixRequestLog.getCurrentRequest()); + assertNull(HystrixRequestLog.getCurrentRequest(strategy)); + assertNull(cmd.currentRequestLog); + } + + /** + * HystrixConcurrencyStrategy + ** useDefaultRequestContext : true + * HystrixCommand + ** useRequestCache : false + ** useRequestLog : false + * + * OUTCOME: RequestLog not set up in command, static access works properly + */ + @Test + public void testCommandDoesNotRequireContextConcurrencyStrategyProvidesItContextSetUpCorrectly() { + HystrixConcurrencyStrategy strategy = new CustomConcurrencyStrategy(true); + HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); + + //context is set up properly + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + HystrixCommand cmd = new TestCommand(false, false); + assertTrue(cmd.execute()); + printRequestLog(); + assertNotNull(HystrixRequestLog.getCurrentRequest()); + assertNotNull(HystrixRequestLog.getCurrentRequest(strategy)); + assertNull(cmd.currentRequestLog); + context.shutdown(); + } + + /** + * HystrixConcurrencyStrategy + ** useDefaultRequestContext : true + * HystrixCommand + ** useRequestCache : false + ** useRequestLog : false + * + * OUTCOME: RequestLog not set up in command, static access is null + */ + @Test + public void testCommandDoesNotRequireContextConcurrencyStrategyProvidesItContextLeftUninitialized() { + HystrixConcurrencyStrategy strategy = new CustomConcurrencyStrategy(true); + HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); + + //context is not set up + HystrixRequestContext.setContextOnCurrentThread(null); + HystrixCommand cmd = new TestCommand(false, false); + assertTrue(cmd.execute()); //command execution not affected by missing context + printRequestLog(); + assertNull(HystrixRequestLog.getCurrentRequest()); + assertNull(HystrixRequestLog.getCurrentRequest(strategy)); + assertNull(cmd.currentRequestLog); + } + + + /** + * HystrixConcurrencyStrategy + ** useDefaultRequestContext : false + * HystrixCommand + ** useRequestCache : false + ** useRequestLog : false + * + * OUTCOME: RequestLog not set up in command, not available statically + */ + @Test + public void testCommandDoesNotRequireContextConcurrencyStrategyDoesNotProvideItContextSetUpCorrectly() { + HystrixConcurrencyStrategy strategy = new CustomConcurrencyStrategy(false); + HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); + + //context is set up properly + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + HystrixCommand cmd = new TestCommand(true, true); + assertTrue(cmd.execute()); + printRequestLog(); + assertNull(HystrixRequestLog.getCurrentRequest()); + assertNull(HystrixRequestLog.getCurrentRequest(strategy)); + assertNull(cmd.currentRequestLog); + context.shutdown(); + } + + /** + * HystrixConcurrencyStrategy + ** useDefaultRequestContext : false + * HystrixCommand + ** useRequestCache : false + ** useRequestLog : false + * + * OUTCOME: RequestLog not set up in command, not available statically + */ + @Test + public void testCommandDoesNotRequireContextConcurrencyStrategyDoesNotProvideItContextLeftUninitialized() { + HystrixConcurrencyStrategy strategy = new CustomConcurrencyStrategy(false); + HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); + + //context is not set up + HystrixRequestContext.setContextOnCurrentThread(null); + HystrixCommand cmd = new TestCommand(true, true); + assertTrue(cmd.execute()); //command execution unaffected by missing context + printRequestLog(); + assertNull(HystrixRequestLog.getCurrentRequest()); + assertNull(HystrixRequestLog.getCurrentRequest(strategy)); + assertNull(cmd.currentRequestLog); + } + + + public static class TestCommand extends HystrixCommand { + + public TestCommand(boolean cacheEnabled, boolean logEnabled) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("TEST")).andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withRequestCacheEnabled(cacheEnabled).withRequestLogEnabled(logEnabled))); + } + + @Override + protected Boolean run() throws Exception { + return true; + } + } + + private static void printRequestLog() { + HystrixRequestLog currentLog = HystrixRequestLog.getCurrentRequest(); + if (currentLog != null) { + System.out.println("RequestLog contents : " + currentLog.getExecutedCommandsAsString()); + } else { + System.out.println(" HystrixRequestLog"); + } + } + + public static class CustomConcurrencyStrategy extends HystrixConcurrencyStrategy { + private final boolean useDefaultRequestContext; + + public CustomConcurrencyStrategy(boolean useDefaultRequestContext) { + this.useDefaultRequestContext = useDefaultRequestContext; + } + + @Override + public Callable wrapCallable(Callable callable) { + return new LoggingCallable(callable); + } + + @Override + public HystrixRequestVariable getRequestVariable(HystrixRequestVariableLifecycle rv) { + if (useDefaultRequestContext) { + //this is the default RequestVariable implementation that requires a HystrixRequestContext + return super.getRequestVariable(rv); + } else { + //this ignores the HystrixRequestContext + return new HystrixRequestVariableDefault() { + @Override + public T initialValue() { + return null; + } + + @Override + public T get() { + return null; + } + + @Override + public void set(T value) { + //do nothing + } + + @Override + public void remove() { + //do nothing + } + + @Override + public void shutdown(T value) { + //do nothing + } + }; + } + } + } + + public static class LoggingCallable implements Callable { + + private final Callable callable; + + public LoggingCallable(Callable callable) { + this.callable = callable; + } + + @Override + public T call() throws Exception { + System.out.println("********start call()"); + return callable.call(); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTimeoutConcurrencyTesting.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTimeoutConcurrencyTesting.java new file mode 100644 index 0000000..4ff4e39 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTimeoutConcurrencyTesting.java @@ -0,0 +1,113 @@ +/** + * 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.Test; + +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import rx.Observable; + +import java.util.ArrayList; +import java.util.List; + +public class HystrixCommandTimeoutConcurrencyTesting { + + private final static int NUM_CONCURRENT_COMMANDS = 30; + + @Test + public void testTimeoutRace() throws InterruptedException { + final int NUM_TRIALS = 10; + + for (int i = 0; i < NUM_TRIALS; i++) { + List> observables = new ArrayList>(); + HystrixRequestContext context = null; + + try { + context = HystrixRequestContext.initializeContext(); + for (int j = 0; j < NUM_CONCURRENT_COMMANDS; j++) { + observables.add(new TestCommand().observe()); + } + + Observable overall = Observable.merge(observables); + + List results = overall.toList().toBlocking().first(); //wait for all commands to complete + + for (String s : results) { + if (s == null) { + System.err.println("Received NULL!"); + throw new RuntimeException("Received NULL"); + } + } + + for (HystrixInvokableInfo hi : HystrixRequestLog.getCurrentRequest().getAllExecutedCommands()) { + if (!hi.isResponseTimedOut()) { + System.err.println("Timeout not found in executed command"); + throw new RuntimeException("Timeout not found in executed command"); + } + if (hi.isResponseTimedOut() && hi.getExecutionEvents().size() == 1) { + System.err.println("Missing fallback status!"); + throw new RuntimeException("Missing fallback status on timeout."); + } + } + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + System.out.println(HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + if (context != null) { + context.shutdown(); + } + } + + System.out.println("*************** TRIAL " + i + " ******************"); + System.out.println(); + Thread.sleep(50); + } + + Hystrix.reset(); + } + + public static class TestCommand extends HystrixCommand { + + protected TestCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("testTimeoutConcurrency")) + .andCommandKey(HystrixCommandKey.Factory.asKey("testTimeoutConcurrencyCommand")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionTimeoutInMilliseconds(3) + .withCircuitBreakerEnabled(false) + .withFallbackIsolationSemaphoreMaxConcurrentRequests(NUM_CONCURRENT_COMMANDS)) + .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() + .withCoreSize(NUM_CONCURRENT_COMMANDS) + .withMaxQueueSize(NUM_CONCURRENT_COMMANDS) + .withQueueSizeRejectionThreshold(NUM_CONCURRENT_COMMANDS))); + } + + @Override + protected String run() throws Exception { + //System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " sleeping"); + Thread.sleep(500); + //System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " awake and returning"); + return "hello"; + } + + @Override + protected String getFallback() { + return "failed"; + } + + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCollapserTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCollapserTest.java new file mode 100644 index 0000000..0839503 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCollapserTest.java @@ -0,0 +1,1874 @@ +/** + * Copyright 2014 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 com.hystrix.junit.HystrixRequestContextRule; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import com.netflix.hystrix.collapser.CollapserTimer; +import com.netflix.hystrix.collapser.RealCollapserTimer; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesCollapserDefault; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; +import com.netflix.hystrix.HystrixCollapserTest.TestCollapserTimer; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +import static org.junit.Assert.*; + +public class HystrixObservableCollapserTest { + private static Action1> onMissingError = new Action1>() { + @Override + public void call(CollapsedRequest collapsedReq) { + collapsedReq.setException(new IllegalStateException("must have a value")); + } + }; + + private static Action1> onMissingThrow = new Action1>() { + @Override + public void call(CollapsedRequest collapsedReq) { + throw new RuntimeException("synchronous error in onMissingResponse handler"); + } + }; + + private static Action1> onMissingComplete = new Action1>() { + @Override + public void call(CollapsedRequest collapsedReq) { + collapsedReq.setComplete(); + } + }; + + private static Action1> onMissingIgnore = new Action1>() { + @Override + public void call(CollapsedRequest collapsedReq) { + //do nothing + } + }; + + private static Action1> onMissingFillIn = new Action1>() { + @Override + public void call(CollapsedRequest collapsedReq) { + collapsedReq.setResponse("fillin"); + } + }; + + private static Func1 prefixMapper = new Func1() { + + @Override + public String call(String s) { + return s.substring(0, s.indexOf(":")); + } + + }; + + private static Func1 map1To3And2To2 = new Func1() { + @Override + public String call(String s) { + String prefix = s.substring(0, s.indexOf(":")); + if (prefix.equals("2")) { + return "2"; + } else { + return "3"; + } + } + }; + + private static Func1 mapWithErrorOn1 = new Func1() { + @Override + public String call(String s) { + String prefix = s.substring(0, s.indexOf(":")); + if (prefix.equals("1")) { + throw new RuntimeException("poorly implemented demultiplexer"); + } else { + return "2"; + } + } + }; + + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + private static ExecutorService threadPool = new ThreadPoolExecutor(100, 100, 10, TimeUnit.MINUTES, new SynchronousQueue()); + + @Before + public void init() { + // since we're going to modify properties of the same class between tests, wipe the cache each time + HystrixCollapser.reset(); + } + + @Test + public void testTwoRequests() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestRequestCollapser(timer, 1); + HystrixObservableCollapser collapser2 = new TestRequestCollapser(timer, 2); + Future response1 = collapser1.observe().toBlocking().toFuture(); + Future response2 = collapser2.observe().toBlocking().toFuture(); + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertEquals("1", response1.get()); + assertEquals("2", response2.get()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + Iterator> cmdIterator = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator(); + assertEquals(2, cmdIterator.next().getNumberCollapsed()); + } + + @Test + public void stressTestRequestCollapser() throws Exception { + for(int i = 0; i < 10; i++) { + init(); + testTwoRequests(); + ctx.reset(); + } + } + + @Test + public void testTwoRequestsWhichShouldEachEmitTwice() throws Exception { + //TestCollapserTimer timer = new TestCollapserTimer(); + CollapserTimer timer = new RealCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 3, false, false, prefixMapper, onMissingComplete); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 3, false, false, prefixMapper, onMissingComplete); + + TestSubscriber testSubscriber1 = new TestSubscriber(); + TestSubscriber testSubscriber2 = new TestSubscriber(); + + System.out.println(System.currentTimeMillis() + "Starting to observe collapser1"); + collapser1.observe().subscribe(testSubscriber1); + collapser2.observe().subscribe(testSubscriber2); + System.out.println(System.currentTimeMillis() + "Done with collapser observe()s"); + + //Note that removing these awaits breaks the unit test. That implies that the above subscribe does not wait for a terminal event + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertCompleted(); + testSubscriber1.assertNoErrors(); + testSubscriber1.assertValues("1:1", "1:2", "1:3"); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("2:2", "2:4", "2:6"); + } + + @Test + public void testTwoRequestsWithErrorProducingBatchCommand() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 3, true); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 3, true); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertError(RuntimeException.class); + testSubscriber1.assertNoValues(); + testSubscriber2.assertError(RuntimeException.class); + testSubscriber2.assertNoValues(); + } + + @Test + public void testTwoRequestsWithErrorInDemultiplex() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 3, false, false, mapWithErrorOn1, onMissingError); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 3, false, false, mapWithErrorOn1, onMissingError); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertError(RuntimeException.class); + testSubscriber1.assertNoValues(); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("2:2", "2:4", "2:6"); + } + + @Test + public void testTwoRequestsWithEmptyResponseAndOnMissingError() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingError); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 0, onMissingError); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertError(IllegalStateException.class); + testSubscriber1.assertNoValues(); + testSubscriber2.assertError(IllegalStateException.class); + testSubscriber2.assertNoValues(); + } + + @Test + public void testTwoRequestsWithEmptyResponseAndOnMissingThrow() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingThrow); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 0, onMissingThrow); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertError(RuntimeException.class); + testSubscriber1.assertNoValues(); + testSubscriber2.assertError(RuntimeException.class); + testSubscriber2.assertNoValues(); + } + + @Test + public void testTwoRequestsWithEmptyResponseAndOnMissingComplete() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingComplete); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 0, onMissingComplete); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertCompleted(); + testSubscriber1.assertNoErrors(); + testSubscriber1.assertNoValues(); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertNoValues(); + } + + @Test + public void testTwoRequestsWithEmptyResponseAndOnMissingIgnore() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingIgnore); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 0, onMissingIgnore); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertCompleted(); + testSubscriber1.assertNoErrors(); + testSubscriber1.assertNoValues(); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertNoValues(); + } + + @Test + public void testTwoRequestsWithEmptyResponseAndOnMissingFillInStaticValue() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingFillIn); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 0, onMissingFillIn); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertCompleted(); + testSubscriber1.assertNoErrors(); + testSubscriber1.assertValues("fillin"); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("fillin"); + } + + @Test + public void testTwoRequestsWithValuesForOneArgOnlyAndOnMissingComplete() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingComplete); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 5, onMissingComplete); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertCompleted(); + testSubscriber1.assertNoErrors(); + testSubscriber1.assertNoValues(); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("2:2", "2:4", "2:6", "2:8", "2:10"); + } + + @Test + public void testTwoRequestsWithValuesForOneArgOnlyAndOnMissingFillInStaticValue() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingFillIn); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 5, onMissingFillIn); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertCompleted(); + testSubscriber1.assertNoErrors(); + testSubscriber1.assertValues("fillin"); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("2:2", "2:4", "2:6", "2:8", "2:10"); + } + + @Test + public void testTwoRequestsWithValuesForOneArgOnlyAndOnMissingIgnore() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingIgnore); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 5, onMissingIgnore); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertCompleted(); + testSubscriber1.assertNoErrors(); + testSubscriber1.assertNoValues(); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("2:2", "2:4", "2:6", "2:8", "2:10"); + } + + @Test + public void testTwoRequestsWithValuesForOneArgOnlyAndOnMissingError() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingError); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 5, onMissingError); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertError(IllegalStateException.class); + testSubscriber1.assertNoValues(); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("2:2", "2:4", "2:6", "2:8", "2:10"); + } + + @Test + public void testTwoRequestsWithValuesForOneArgOnlyAndOnMissingThrow() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 0, onMissingThrow); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 5, onMissingThrow); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertError(RuntimeException.class); + testSubscriber1.assertNoValues(); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("2:2", "2:4", "2:6", "2:8", "2:10"); + } + + @Test + public void testTwoRequestsWithValuesForWrongArgs() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 3, false, false, map1To3And2To2, onMissingError); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 3, false, false, map1To3And2To2, onMissingError); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertError(RuntimeException.class); + testSubscriber1.assertNoValues(); + testSubscriber2.assertCompleted(); + testSubscriber2.assertNoErrors(); + testSubscriber2.assertValues("2:2", "2:4", "2:6"); + } + + @Test + public void testTwoRequestsWhenBatchCommandFails() { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestCollapserWithMultipleResponses(timer, 1, 3, false, true, map1To3And2To2, onMissingError); + HystrixObservableCollapser collapser2 = new TestCollapserWithMultipleResponses(timer, 2, 3, false, true, map1To3And2To2, onMissingError); + + System.out.println("Starting to observe collapser1"); + Observable result1 = collapser1.observe(); + Observable result2 = collapser2.observe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + TestSubscriber testSubscriber1 = new TestSubscriber(); + result1.subscribe(testSubscriber1); + + TestSubscriber testSubscriber2 = new TestSubscriber(); + result2.subscribe(testSubscriber2); + + testSubscriber1.awaitTerminalEvent(); + testSubscriber2.awaitTerminalEvent(); + + testSubscriber1.assertError(RuntimeException.class); + testSubscriber1.getOnErrorEvents().get(0).printStackTrace(); + testSubscriber1.assertNoValues(); + testSubscriber2.assertError(RuntimeException.class); + testSubscriber2.assertNoValues(); + } + + @Test + public void testCollapserUnderConcurrency() throws InterruptedException { + final CollapserTimer timer = new RealCollapserTimer(); + + final int NUM_THREADS_SUBMITTING_WORK = 8; + final int NUM_REQUESTS_PER_THREAD = 8; + + final CountDownLatch latch = new CountDownLatch(NUM_THREADS_SUBMITTING_WORK); + + List runnables = new ArrayList(); + final ConcurrentLinkedQueue> subscribers = new ConcurrentLinkedQueue>(); + + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + + final AtomicInteger uniqueInt = new AtomicInteger(0); + + for (int i = 0; i < NUM_THREADS_SUBMITTING_WORK; i++) { + runnables.add(new Runnable() { + @Override + public void run() { + try { + //System.out.println("Runnable starting on thread : " + Thread.currentThread().getName()); + + for (int j = 0; j < NUM_REQUESTS_PER_THREAD; j++) { + HystrixObservableCollapser collapser = + new TestCollapserWithMultipleResponses(timer, uniqueInt.getAndIncrement(), 3, false); + Observable o = collapser.toObservable(); + TestSubscriber subscriber = new TestSubscriber(); + o.subscribe(subscriber); + subscribers.offer(subscriber); + } + //System.out.println("Runnable done on thread : " + Thread.currentThread().getName()); + } finally { + latch.countDown(); + } + } + }); + } + + for (Runnable r: runnables) { + threadPool.submit(new HystrixContextRunnable(r)); + } + + assertTrue(latch.await(1000, TimeUnit.MILLISECONDS)); + + for (TestSubscriber subscriber: subscribers) { + subscriber.awaitTerminalEvent(); + if (subscriber.getOnErrorEvents().size() > 0) { + System.out.println("ERROR : " + subscriber.getOnErrorEvents()); + for (Throwable ex: subscriber.getOnErrorEvents()) { + ex.printStackTrace(); + } + } + subscriber.assertCompleted(); + subscriber.assertNoErrors(); + System.out.println("Received : " + subscriber.getOnNextEvents()); + subscriber.assertValueCount(3); + } + + context.shutdown(); + } + + @Test + public void testConcurrencyInLoop() throws InterruptedException { + for (int i = 0; i < 10; i++) { + System.out.println("TRIAL : " + i); + testCollapserUnderConcurrency(); + } + } + + @Test + public void testEarlyUnsubscribeExecutedViaToObservable() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestRequestCollapser(timer, 1); + Observable response1 = collapser1.toObservable(); + HystrixObservableCollapser collapser2 = new TestRequestCollapser(timer, 2); + Observable response2 = collapser2.toObservable(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s1.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertEquals("2", value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixCollapserMetrics metrics = collapser1.getMetrics(); + assertSame(metrics, collapser2.getMetrics()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertEquals(1, command.getNumberCollapsed()); //1 should have been removed from batch + } + + @Test + public void testEarlyUnsubscribeExecutedViaObserve() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestRequestCollapser(timer, 1); + Observable response1 = collapser1.observe(); + HystrixObservableCollapser collapser2 = new TestRequestCollapser(timer, 2); + Observable response2 = collapser2.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s1.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertEquals("2", value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixCollapserMetrics metrics = collapser1.getMetrics(); + assertSame(metrics, collapser2.getMetrics()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertEquals(1, command.getNumberCollapsed()); //1 should have been removed from batch + } + + @Test + public void testEarlyUnsubscribeFromAllCancelsBatch() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new TestRequestCollapser(timer, 1); + Observable response1 = collapser1.observe(); + HystrixObservableCollapser collapser2 = new TestRequestCollapser(timer, 2); + Observable response2 = collapser2.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s1.unsubscribe(); + s2.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertNull(value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testRequestThenCacheHitAndCacheHitUnsubscribed() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response1 = collapser1.observe(); + HystrixObservableCollapser collapser2 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response2 = collapser2.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s2.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertEquals("foo", value1.get()); + assertNull(value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.COLLAPSED); + assertEquals(1, command.getNumberCollapsed()); //should only be 1 collapsed - other came from cache, then was cancelled + } + + @Test + public void testRequestThenCacheHitAndOriginalUnsubscribed() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response1 = collapser1.observe(); + HystrixObservableCollapser collapser2 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response2 = collapser2.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + s1.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertEquals("foo", value2.get()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.COLLAPSED); + assertEquals(1, command.getNumberCollapsed()); //should only be 1 collapsed - other came from cache, then was cancelled + } + + @Test + public void testRequestThenTwoCacheHitsOriginalAndOneCacheHitUnsubscribed() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response1 = collapser1.observe(); + HystrixObservableCollapser collapser2 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response2 = collapser2.observe(); + HystrixObservableCollapser collapser3 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response3 = collapser3.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final CountDownLatch latch3 = new CountDownLatch(1); + + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + final AtomicReference value3 = new AtomicReference(null); + + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + Subscription s3 = response3 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s3 Unsubscribed!"); + latch3.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s3 OnCompleted"); + latch3.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s3 OnError : " + e); + latch3.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s3 OnNext : " + s); + value3.set(s); + } + }); + + s1.unsubscribe(); + s3.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertEquals("foo", value2.get()); + assertNull(value3.get()); + + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().iterator().next(); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.COLLAPSED); + assertEquals(1, command.getNumberCollapsed()); //should only be 1 collapsed - other came from cache, then was cancelled + } + + @Test + public void testRequestThenTwoCacheHitsAllUnsubscribed() throws Exception { + TestCollapserTimer timer = new TestCollapserTimer(); + HystrixObservableCollapser collapser1 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response1 = collapser1.observe(); + HystrixObservableCollapser collapser2 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response2 = collapser2.observe(); + HystrixObservableCollapser collapser3 = new SuccessfulCacheableCollapsedCommand(timer, "foo", true); + Observable response3 = collapser3.observe(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final CountDownLatch latch3 = new CountDownLatch(1); + + + final AtomicReference value1 = new AtomicReference(null); + final AtomicReference value2 = new AtomicReference(null); + final AtomicReference value3 = new AtomicReference(null); + + + Subscription s1 = response1 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s1 Unsubscribed!"); + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s1 OnNext : " + s); + value1.set(s); + } + }); + + Subscription s2 = response2 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s2 Unsubscribed!"); + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s2 OnNext : " + s); + value2.set(s); + } + }); + + Subscription s3 = response3 + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : s3 Unsubscribed!"); + latch3.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : s3 OnCompleted"); + latch3.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : s3 OnError : " + e); + latch3.countDown(); + } + + @Override + public void onNext(String s) { + System.out.println(System.currentTimeMillis() + " : s3 OnNext : " + s); + value3.set(s); + } + }); + + s1.unsubscribe(); + s2.unsubscribe(); + s3.unsubscribe(); + + timer.incrementTime(10); // let time pass that equals the default delay/period + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + + assertNull(value1.get()); + assertNull(value2.get()); + assertNull(value3.get()); + + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + class Pair { + final A a; + final B b; + + Pair(A a, B b) { + this.a = a; + this.b = b; + } + } + + class MyCommand extends HystrixObservableCommand> { + + private final List args; + + public MyCommand(List args) { + super(HystrixObservableCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("BATCH"))); + this.args = args; + } + + @Override + protected Observable> construct() { + return Observable.from(args).map(new Func1>() { + @Override + public Pair call(String s) { + return new Pair(s, Integer.parseInt(s)); + } + }); + } + } + + class MyCollapser extends HystrixObservableCollapser, Integer, String> { + + private final String arg; + + public MyCollapser(String arg, boolean requestCachingOn) { + super(HystrixCollapserKey.Factory.asKey("UNITTEST"), + HystrixObservableCollapser.Scope.REQUEST, + new RealCollapserTimer(), + HystrixCollapserProperties.Setter().withRequestCacheEnabled(requestCachingOn), + HystrixCollapserMetrics.getInstance(HystrixCollapserKey.Factory.asKey("UNITTEST"), + new HystrixPropertiesCollapserDefault(HystrixCollapserKey.Factory.asKey("UNITTEST"), + HystrixCollapserProperties.Setter()))); + this.arg = arg; + } + + + @Override + public String getRequestArgument() { + return arg; + } + + @Override + protected HystrixObservableCommand> createCommand(Collection> collapsedRequests) { + List args = new ArrayList(); + for (CollapsedRequest req: collapsedRequests) { + args.add(req.getArgument()); + } + + return new MyCommand(args); + } + + @Override + protected Func1, String> getBatchReturnTypeKeySelector() { + return new Func1, String>() { + @Override + public String call(Pair pair) { + return pair.a; + } + }; + } + + @Override + protected Func1 getRequestArgumentKeySelector() { + return new Func1() { + @Override + public String call(String s) { + return s; + } + }; + } + + @Override + protected void onMissingResponse(CollapsedRequest r) { + r.setException(new RuntimeException("missing")); + } + + @Override + protected Func1, Integer> getBatchReturnTypeToResponseTypeMapper() { + return new Func1, Integer>() { + @Override + public Integer call(Pair pair) { + return pair.b; + } + }; + } + } + + @Test + public void testDuplicateArgumentsWithRequestCachingOn() throws Exception { + final int NUM = 10; + + List> observables = new ArrayList>(); + for (int i = 0; i < NUM; i++) { + MyCollapser c = new MyCollapser("5", true); + observables.add(c.toObservable()); + } + + List> subscribers = new ArrayList>(); + for (final Observable o: observables) { + final TestSubscriber sub = new TestSubscriber(); + subscribers.add(sub); + + o.subscribe(sub); + } + + Thread.sleep(100); + + //all subscribers should receive the same value + for (TestSubscriber sub: subscribers) { + sub.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + System.out.println("Subscriber received : " + sub.getOnNextEvents()); + sub.assertCompleted(); + sub.assertNoErrors(); + sub.assertValues(5); + } + } + + @Test + public void testDuplicateArgumentsWithRequestCachingOff() throws Exception { + final int NUM = 10; + + List> observables = new ArrayList>(); + for (int i = 0; i < NUM; i++) { + MyCollapser c = new MyCollapser("5", false); + observables.add(c.toObservable()); + } + + List> subscribers = new ArrayList>(); + for (final Observable o: observables) { + final TestSubscriber sub = new TestSubscriber(); + subscribers.add(sub); + + o.subscribe(sub); + } + + Thread.sleep(100); + + AtomicInteger numErrors = new AtomicInteger(0); + AtomicInteger numValues = new AtomicInteger(0); + + // only the first subscriber should receive the value. + // the others should get an error that the batch contains duplicates + for (TestSubscriber sub: subscribers) { + sub.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + if (sub.getOnCompletedEvents().isEmpty()) { + System.out.println(Thread.currentThread().getName() + " Error : " + sub.getOnErrorEvents()); + sub.assertError(IllegalArgumentException.class); + sub.assertNoValues(); + numErrors.getAndIncrement(); + + } else { + System.out.println(Thread.currentThread().getName() + " OnNext : " + sub.getOnNextEvents()); + sub.assertValues(5); + sub.assertCompleted(); + sub.assertNoErrors(); + numValues.getAndIncrement(); + } + } + + assertEquals(1, numValues.get()); + assertEquals(NUM - 1, numErrors.get()); + } + + protected void assertCommandExecutionEvents(HystrixInvokableInfo command, HystrixEventType... expectedEventTypes) { + boolean emitExpected = false; + int expectedEmitCount = 0; + + boolean fallbackEmitExpected = false; + int expectedFallbackEmitCount = 0; + + List condensedEmitExpectedEventTypes = new ArrayList(); + + for (HystrixEventType expectedEventType: expectedEventTypes) { + if (expectedEventType.equals(HystrixEventType.EMIT)) { + if (!emitExpected) { + //first EMIT encountered, add it to condensedEmitExpectedEventTypes + condensedEmitExpectedEventTypes.add(HystrixEventType.EMIT); + } + emitExpected = true; + expectedEmitCount++; + } else if (expectedEventType.equals(HystrixEventType.FALLBACK_EMIT)) { + if (!fallbackEmitExpected) { + //first FALLBACK_EMIT encountered, add it to condensedEmitExpectedEventTypes + condensedEmitExpectedEventTypes.add(HystrixEventType.FALLBACK_EMIT); + } + fallbackEmitExpected = true; + expectedFallbackEmitCount++; + } else { + condensedEmitExpectedEventTypes.add(expectedEventType); + } + } + List actualEventTypes = command.getExecutionEvents(); + assertEquals(expectedEmitCount, command.getNumberEmissions()); + assertEquals(expectedFallbackEmitCount, command.getNumberFallbackEmissions()); + assertEquals(condensedEmitExpectedEventTypes, actualEventTypes); + } + + private static class TestRequestCollapser extends HystrixObservableCollapser { + + private final String value; + private ConcurrentLinkedQueue> commandsExecuted; + + public TestRequestCollapser(TestCollapserTimer timer, int value) { + this(timer, String.valueOf(value)); + } + + public TestRequestCollapser(TestCollapserTimer timer, String value) { + this(timer, value, 10000, 10); + } + + public TestRequestCollapser(TestCollapserTimer timer, String value, ConcurrentLinkedQueue> executionLog) { + this(timer, value, 10000, 10, executionLog); + } + + public TestRequestCollapser(TestCollapserTimer timer, int value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds) { + this(timer, String.valueOf(value), defaultMaxRequestsInBatch, defaultTimerDelayInMilliseconds); + } + + public TestRequestCollapser(TestCollapserTimer timer, String value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds) { + this(timer, value, defaultMaxRequestsInBatch, defaultTimerDelayInMilliseconds, null); + } + + public TestRequestCollapser(Scope scope, TestCollapserTimer timer, String value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds) { + this(scope, timer, value, defaultMaxRequestsInBatch, defaultTimerDelayInMilliseconds, null); + } + + public TestRequestCollapser(TestCollapserTimer timer, String value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds, ConcurrentLinkedQueue> executionLog) { + this(Scope.REQUEST, timer, value, defaultMaxRequestsInBatch, defaultTimerDelayInMilliseconds, executionLog); + } + + private static HystrixCollapserMetrics createMetrics() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("COLLAPSER_ONE"); + return HystrixCollapserMetrics.getInstance(key, new HystrixPropertiesCollapserDefault(key, HystrixCollapserProperties.Setter())); + } + + public TestRequestCollapser(Scope scope, TestCollapserTimer timer, String value, int defaultMaxRequestsInBatch, int defaultTimerDelayInMilliseconds, ConcurrentLinkedQueue> executionLog) { + // use a CollapserKey based on the CollapserTimer object reference so it's unique for each timer as we don't want caching + // of properties to occur and we're using the default HystrixProperty which typically does caching + super(collapserKeyFromString(timer), scope, timer, HystrixCollapserProperties.Setter().withMaxRequestsInBatch(defaultMaxRequestsInBatch).withTimerDelayInMilliseconds(defaultTimerDelayInMilliseconds), createMetrics()); + this.value = value; + this.commandsExecuted = executionLog; + } + + @Override + public String getRequestArgument() { + return value; + } + + @Override + public HystrixObservableCommand createCommand(final Collection> requests) { + /* return a mocked command */ + HystrixObservableCommand command = new TestCollapserCommand(requests); + if (commandsExecuted != null) { + commandsExecuted.add(command); + } + return command; + } + + @Override + protected Func1 getBatchReturnTypeToResponseTypeMapper() { + return new Func1() { + + @Override + public String call(String s) { + return s; + } + + }; + } + + @Override + protected Func1 getBatchReturnTypeKeySelector() { + return new Func1() { + + @Override + public String call(String s) { + return s; + } + + }; + } + + @Override + protected Func1 getRequestArgumentKeySelector() { + return new Func1() { + + @Override + public String call(String s) { + return s; + } + + }; + } + + @Override + protected void onMissingResponse(CollapsedRequest r) { + r.setException(new RuntimeException("missing value!")); + } + } + + private static HystrixCollapserKey collapserKeyFromString(final Object o) { + return new HystrixCollapserKey() { + + @Override + public String name() { + return String.valueOf(o); + } + + }; + } + + private static class TestCollapserCommand extends TestHystrixObservableCommand { + + private final Collection> requests; + + TestCollapserCommand(Collection> requests) { + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(1000))); + this.requests = requests; + } + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + System.out.println(">>> TestCollapserCommand run() ... batch size: " + requests.size()); + // simulate a batch request + for (CollapsedRequest request : requests) { + if (request.getArgument() == null) { + throw new NullPointerException("Simulated Error"); + } + if (request.getArgument().equals("TIMEOUT")) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + s.onNext(request.getArgument()); + } + + s.onCompleted(); + } + + }).subscribeOn(Schedulers.computation()); + } + + } + + private static class TestCollapserWithMultipleResponses extends HystrixObservableCollapser { + + private final String arg; + private final static ConcurrentMap emitsPerArg; + private final boolean commandConstructionFails; + private final boolean commandExecutionFails; + private final Func1 keyMapper; + private final Action1> onMissingResponseHandler; + + private final static HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("COLLAPSER_MULTI"); + private final static HystrixCollapserProperties.Setter propsSetter = HystrixCollapserProperties.Setter().withMaxRequestsInBatch(10).withTimerDelayInMilliseconds(10); + private final static HystrixCollapserMetrics metrics = HystrixCollapserMetrics.getInstance(key, new HystrixPropertiesCollapserDefault(key, HystrixCollapserProperties.Setter())); + + static { + emitsPerArg = new ConcurrentHashMap(); + } + + public TestCollapserWithMultipleResponses(CollapserTimer timer, int arg, int numEmits, boolean commandConstructionFails) { + this(timer, arg, numEmits, commandConstructionFails, false, prefixMapper, onMissingComplete); + } + + public TestCollapserWithMultipleResponses(CollapserTimer timer, int arg, int numEmits, Action1> onMissingHandler) { + this(timer, arg, numEmits, false, false, prefixMapper, onMissingHandler); + } + + public TestCollapserWithMultipleResponses(CollapserTimer timer, int arg, int numEmits, Func1 keyMapper) { + this(timer, arg, numEmits, false, false, keyMapper, onMissingComplete); + } + + public TestCollapserWithMultipleResponses(CollapserTimer timer, int arg, int numEmits, boolean commandConstructionFails, boolean commandExecutionFails, Func1 keyMapper, Action1> onMissingResponseHandler) { + super(collapserKeyFromString(timer), Scope.REQUEST, timer, propsSetter, metrics); + this.arg = arg + ""; + emitsPerArg.put(this.arg, numEmits); + this.commandConstructionFails = commandConstructionFails; + this.commandExecutionFails = commandExecutionFails; + this.keyMapper = keyMapper; + this.onMissingResponseHandler = onMissingResponseHandler; + } + + @Override + public String getRequestArgument() { + return arg; + } + + @Override + protected HystrixObservableCommand createCommand(Collection> collapsedRequests) { + assertNotNull("command creation should have HystrixRequestContext", HystrixRequestContext.getContextForCurrentThread()); + if (commandConstructionFails) { + throw new RuntimeException("Exception thrown in command construction"); + } else { + List args = new ArrayList(); + + for (CollapsedRequest collapsedRequest : collapsedRequests) { + String stringArg = collapsedRequest.getArgument(); + int intArg = Integer.parseInt(stringArg); + args.add(intArg); + } + + return new TestCollapserCommandWithMultipleResponsePerArgument(args, emitsPerArg, commandExecutionFails); + } + } + + //Data comes back in the form: 1:1, 1:2, 1:3, 2:2, 2:4, 2:6. + //This method should use the first half of that string as the request arg + @Override + protected Func1 getBatchReturnTypeKeySelector() { + return keyMapper; + + } + + @Override + protected Func1 getRequestArgumentKeySelector() { + return new Func1() { + + @Override + public String call(String s) { + return s; + } + + }; + } + + @Override + protected void onMissingResponse(CollapsedRequest r) { + onMissingResponseHandler.call(r); + + } + + @Override + protected Func1 getBatchReturnTypeToResponseTypeMapper() { + return new Func1() { + + @Override + public String call(String s) { + return s; + } + + }; + } + } + + private static class TestCollapserCommandWithMultipleResponsePerArgument extends TestHystrixObservableCommand { + + private final List args; + private final Map emitsPerArg; + private final boolean commandExecutionFails; + + private static InspectableBuilder.TestCommandBuilder setter = testPropsBuilder(); + + TestCollapserCommandWithMultipleResponsePerArgument(List args, Map emitsPerArg, boolean commandExecutionFails) { + super(setter); + this.args = args; + this.emitsPerArg = emitsPerArg; + this.commandExecutionFails = commandExecutionFails; + } + + @Override + protected Observable construct() { + assertNotNull("Wiring the Batch command into the Observable chain should have a HystrixRequestContext", HystrixRequestContext.getContextForCurrentThread()); + if (commandExecutionFails) { + return Observable.error(new RuntimeException("Synthetic error while running batch command")); + } else { + return Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + assertNotNull("Executing the Batch command should have a HystrixRequestContext", HystrixRequestContext.getContextForCurrentThread()); + Thread.sleep(1); + for (Integer arg : args) { + int numEmits = emitsPerArg.get(arg.toString()); + for (int j = 1; j < numEmits + 1; j++) { + subscriber.onNext(arg + ":" + (arg * j)); + Thread.sleep(1); + } + Thread.sleep(1); + } + subscriber.onCompleted(); + } catch (Throwable ex) { + ex.printStackTrace(); + subscriber.onError(ex); + } + } + }); + } + } + } + + /** + * A Command implementation that supports caching. + */ + private static class SuccessfulCacheableCollapsedCommand extends TestRequestCollapser { + + private final boolean cacheEnabled; + + public SuccessfulCacheableCollapsedCommand(TestCollapserTimer timer, String value, boolean cacheEnabled) { + super(timer, value); + this.cacheEnabled = cacheEnabled; + } + + @Override + public String getCacheKey() { + if (cacheEnabled) + return "aCacheKey_" + super.value; + else + return null; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java new file mode 100644 index 0000000..f455f6b --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java @@ -0,0 +1,5735 @@ +/** + * 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 com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.AbstractCommand.TryableSemaphoreActual; +import com.netflix.hystrix.HystrixCircuitBreakerTest.TestCircuitBreaker; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import rx.*; +import rx.Observable.OnSubscribe; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class HystrixObservableCommandTest extends CommonHystrixCommandTests> { + + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + @After + public void cleanup() { + // force properties to be clean as well + ConfigurationManager.getConfigInstance().clear(); + + /* + * RxJava will create one worker for each processor when we schedule Observables in the + * Schedulers.computation(). Any leftovers here might lead to a congestion in a following + * thread. To ensure all existing threads have completed we now schedule some observables + * that will execute in distinct threads due to the latch.. + */ + int count = Runtime.getRuntime().availableProcessors(); + final CountDownLatch latch = new CountDownLatch(count); + ArrayList> futures = new ArrayList>(); + for (int i = 0; i < count; ++i) { + futures.add(Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber sub) { + latch.countDown(); + try { + latch.await(); + + sub.onNext(true); + sub.onCompleted(); + } catch (InterruptedException e) { + sub.onError(e); + } + } + }).subscribeOn(Schedulers.computation()).toBlocking().toFuture()); + } + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + //TODO commented out as it has issues when built from command-line even though it works from IDE + // HystrixCommandKey key = Hystrix.getCurrentThreadExecutingCommand(); + // if (key != null) { + // throw new IllegalStateException("should be null but got: " + key); + // } + } + + class CompletableCommand extends HystrixObservableCommand { + + CompletableCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("COMPLETABLE"))); + } + + @Override + protected Observable construct() { + return Completable.complete().toObservable(); + } + } + + @Test + public void testCompletable() throws InterruptedException { + + + final CountDownLatch latch = new CountDownLatch(1); + final HystrixObservableCommand command = new CompletableCommand(); + + command.observe().subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnError : " + e); + latch.countDown(); + } + + @Override + public void onNext(Integer integer) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnNext : " + integer); + } + }); + + latch.await(); + assertEquals(null, command.getFailedExecutionException()); + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + assertFalse(command.isResponseFromFallback()); + assertCommandExecutionEvents(command, HystrixEventType.SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertNull(command.getExecutionException()); + } + + /** + * Test a successful semaphore-isolated command execution. + */ + @Test + public void testSemaphoreObserveSuccess() { + testObserveSuccess(ExecutionIsolationStrategy.SEMAPHORE); + } + + /** + * Test a successful thread-isolated command execution. + */ + @Test + public void testThreadObserveSuccess() { + testObserveSuccess(ExecutionIsolationStrategy.THREAD); + } + + private void testObserveSuccess(ExecutionIsolationStrategy isolationStrategy) { + try { + TestHystrixObservableCommand command = new SuccessfulTestCommand(isolationStrategy); + assertEquals(true, command.observe().toBlocking().single()); + + assertEquals(null, command.getFailedExecutionException()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + assertFalse(command.isResponseFromFallback()); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertNull(command.getExecutionException()); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test that a semaphore command can not be executed multiple times. + */ + @Test + public void testSemaphoreIsolatedObserveMultipleTimes() { + testObserveMultipleTimes(ExecutionIsolationStrategy.SEMAPHORE); + } + + /** + * Test that a thread command can not be executed multiple times. + */ + @Test + public void testThreadIsolatedObserveMultipleTimes() { + testObserveMultipleTimes(ExecutionIsolationStrategy.THREAD); + } + + private void testObserveMultipleTimes(ExecutionIsolationStrategy isolationStrategy) { + SuccessfulTestCommand command = new SuccessfulTestCommand(isolationStrategy); + assertFalse(command.isExecutionComplete()); + // first should succeed + assertEquals(true, command.observe().toBlocking().single()); + assertTrue(command.isExecutionComplete()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + assertFalse(command.isResponseFromFallback()); + assertNull(command.getExecutionException()); + + try { + // second should fail + command.observe().toBlocking().single(); + fail("we should not allow this ... it breaks the state of request logs"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + // we want to get here + } + + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + assertSaneHystrixRequestLog(1); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + } + + /** + * Test a semaphore command execution that throws an HystrixException synchronously and didn't implement getFallback. + */ + @Test + public void testSemaphoreIsolatedObserveKnownSyncFailureWithNoFallback() { + testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE, false); + } + + /** + * Test a semaphore command execution that throws an HystrixException asynchronously and didn't implement getFallback. + */ + @Test + public void testSemaphoreIsolatedObserveKnownAsyncFailureWithNoFallback() { + testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE, true); + } + + /** + * Test a thread command execution that throws an HystrixException synchronously and didn't implement getFallback. + */ + @Test + public void testThreadIsolatedObserveKnownSyncFailureWithNoFallback() { + testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy.THREAD, false); + } + + /** + * Test a thread command execution that throws an HystrixException asynchronously and didn't implement getFallback. + */ + @Test + public void testThreadIsolatedObserveKnownAsyncFailureWithNoFallback() { + testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy.THREAD, true); + } + + private void testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + TestHystrixObservableCommand command = new KnownFailureTestCommandWithoutFallback(circuitBreaker, isolationStrategy, asyncException); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertNotNull(e.getFallbackException()); + assertNotNull(e.getImplementingClass()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should always get an HystrixRuntimeException when an error occurs."); + } + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertFalse(command.isResponseFromFallback()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } + + /** + * Test a semaphore command execution that throws an unknown exception (not HystrixException) synchronously and didn't implement getFallback. + */ + @Test + public void testSemaphoreIsolatedObserveUnknownSyncFailureWithNoFallback() { + testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE, false); + } + + /** + * Test a semaphore command execution that throws an unknown exception (not HystrixException) asynchronously and didn't implement getFallback. + */ + @Test + public void testSemaphoreIsolatedObserveUnknownAsyncFailureWithNoFallback() { + testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE, true); + } + + /** + * Test a thread command execution that throws an unknown exception (not HystrixException) synchronously and didn't implement getFallback. + */ + @Test + public void testThreadIsolatedObserveUnknownSyncFailureWithNoFallback() { + testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy.THREAD, false); + } + + /** + * Test a thread command execution that throws an unknown exception (not HystrixException) asynchronously and didn't implement getFallback. + */ + @Test + public void testThreadIsolatedObserveUnknownAsyncFailureWithNoFallback() { + testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy.THREAD, true); + } + + private void testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + TestHystrixObservableCommand command = new UnknownFailureTestCommandWithoutFallback(isolationStrategy, asyncException); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertNotNull(e.getFallbackException()); + assertNotNull(e.getImplementingClass()); + + } catch (Exception e) { + e.printStackTrace(); + fail("We should always get an HystrixRuntimeException when an error occurs."); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertFalse(command.isResponseFromFallback()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertNotNull(command.getExecutionException()); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } + + /** + * Test a semaphore command execution that fails synchronously but has a fallback. + */ + @Test + public void testSemaphoreIsolatedObserveSyncFailureWithFallback() { + testObserveFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, false); + } + + /** + * Test a semaphore command execution that fails asynchronously but has a fallback. + */ + @Test + public void testSemaphoreIsolatedObserveAsyncFailureWithFallback() { + testObserveFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, true); + } + + /** + * Test a thread command execution that fails synchronously but has a fallback. + */ + @Test + public void testThreadIsolatedObserveSyncFailureWithFallback() { + testObserveFailureWithFallback(ExecutionIsolationStrategy.THREAD, false); + } + + /** + * Test a thread command execution that fails asynchronously but has a fallback. + */ + @Test + public void testThreadIsolatedObserveAsyncFailureWithFallback() { + testObserveFailureWithFallback(ExecutionIsolationStrategy.THREAD, true); + } + + private void testObserveFailureWithFallback(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + TestHystrixObservableCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), isolationStrategy, asyncException); + try { + assertEquals(false, command.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertEquals("we failed with a simulated issue", command.getFailedExecutionException().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertTrue(command.isResponseFromFallback()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertNotNull(command.getExecutionException()); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } + + /** + * Test a command execution that fails synchronously, has getFallback implemented but that fails as well (synchronously). + */ + @Test + public void testSemaphoreIsolatedObserveSyncFailureWithSyncFallbackFailure() { + testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, false, false); + } + + /** + * Test a command execution that fails synchronously, has getFallback implemented but that fails as well (asynchronously). + */ + @Test + public void testSemaphoreIsolatedObserveSyncFailureWithAsyncFallbackFailure() { + testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, false, true); + } + + /** + * Test a command execution that fails asynchronously, has getFallback implemented but that fails as well (synchronously). + */ + @Test + public void testSemaphoreIsolatedObserveAyncFailureWithSyncFallbackFailure() { + testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, true, false); + } + + /** + * Test a command execution that fails asynchronously, has getFallback implemented but that fails as well (asynchronously). + */ + @Test + public void testSemaphoreIsolatedObserveAsyncFailureWithAsyncFallbackFailure() { + testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, true, true); + } + + /** + * Test a command execution that fails synchronously, has getFallback implemented but that fails as well (synchronously). + */ + @Test + public void testThreadIsolatedObserveSyncFailureWithSyncFallbackFailure() { + testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.THREAD, false, false); + } + + /** + * Test a command execution that fails synchronously, has getFallback implemented but that fails as well (asynchronously). + */ + @Test + public void testThreadIsolatedObserveSyncFailureWithAsyncFallbackFailure() { + testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.THREAD, true, false); + } + + /** + * Test a command execution that fails asynchronously, has getFallback implemented but that fails as well (synchronously). + */ + @Test + public void testThreadIsolatedObserveAyncFailureWithSyncFallbackFailure() { + testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.THREAD, false, true); + } + + /** + * Test a command execution that fails asynchronously, has getFallback implemented but that fails as well (asynchronously). + */ + @Test + public void testThreadIsolatedObserveAsyncFailureWithAsyncFallbackFailure() { + testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.THREAD, true, true); + } + + + private void testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy isolationStrategy, boolean asyncFallbackException, boolean asyncConstructException) { + TestHystrixObservableCommand command = new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker(), isolationStrategy, asyncConstructException, asyncFallbackException); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + System.out.println("------------------------------------------------"); + e.printStackTrace(); + System.out.println("------------------------------------------------"); + assertNotNull(e.getFallbackException()); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertFalse(command.isResponseFromFallback()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_FAILURE); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertNotNull(command.getExecutionException()); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } + + /** + * Test a semaphore command execution that times out with a fallback and eventually succeeds. + */ + @Test + public void testSemaphoreIsolatedObserveTimeoutWithSuccessAndFallback() { + testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy.SEMAPHORE, TestHystrixObservableCommand.ExecutionResult.MULTIPLE_EMITS_THEN_SUCCESS); + } + + /** + * Test a semaphore command execution that times out with a fallback and eventually fails. + */ + @Test + public void testSemaphoreIsolatedObserveTimeoutWithFailureAndFallback() { + testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy.SEMAPHORE, TestHystrixObservableCommand.ExecutionResult.MULTIPLE_EMITS_THEN_FAILURE); + } + + /** + * Test a thread command execution that times out with a fallback and eventually succeeds. + */ + @Test + public void testThreadIsolatedObserveTimeoutWithSuccessAndFallback() { + testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy.THREAD, TestHystrixObservableCommand.ExecutionResult.MULTIPLE_EMITS_THEN_SUCCESS); + } + + /** + * Test a thread command execution that times out with a fallback and eventually fails. + */ + @Test + public void testThreadIsolatedObserveTimeoutWithFailureAndFallback() { + testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy.THREAD, TestHystrixObservableCommand.ExecutionResult.MULTIPLE_EMITS_THEN_FAILURE); + } + + private void testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy isolationStrategy, TestHystrixObservableCommand.ExecutionResult executionResult) { + TestHystrixObservableCommand command = getCommand(isolationStrategy, executionResult, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 100); + long observedCommandDuration = 0; + try { + long startTime = System.currentTimeMillis(); + assertEquals(FlexibleTestHystrixObservableCommand.FALLBACK_VALUE, command.observe().toBlocking().single()); + observedCommandDuration = System.currentTimeMillis() - startTime; + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertNull(command.getFailedExecutionException()); + assertNotNull(command.getExecutionException()); + + System.out.println("Command time : " + command.getExecutionTimeInMilliseconds()); + System.out.println("Observed command time : " + observedCommandDuration); + assertTrue(command.getExecutionTimeInMilliseconds() >= 100); + assertTrue(observedCommandDuration >= 100); + assertTrue(command.getExecutionTimeInMilliseconds() < 1000); + assertTrue(observedCommandDuration < 1000); + assertFalse(command.isFailedExecution()); + assertTrue(command.isResponseFromFallback()); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } + + /** + * Test a successful command execution. + */ + @Test + public void testObserveOnImmediateSchedulerByDefaultForSemaphoreIsolation() throws Exception { + + final AtomicReference commandThread = new AtomicReference(); + final AtomicReference subscribeThread = new AtomicReference(); + + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))) { + + @Override + protected Observable construct() { + commandThread.set(Thread.currentThread()); + return Observable.just(true); + } + }; + + final CountDownLatch latch = new CountDownLatch(1); + + command.toObservable().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + + } + + @Override + public void onError(Throwable e) { + latch.countDown(); + e.printStackTrace(); + + } + + @Override + public void onNext(Boolean args) { + subscribeThread.set(Thread.currentThread()); + } + }); + + if (!latch.await(2000, TimeUnit.MILLISECONDS)) { + fail("timed out"); + } + + assertNotNull(commandThread.get()); + assertNotNull(subscribeThread.get()); + + System.out.println("Command Thread: " + commandThread.get()); + System.out.println("Subscribe Thread: " + subscribeThread.get()); + + String mainThreadName = Thread.currentThread().getName(); + + // semaphore should be on the calling thread + assertTrue(commandThread.get().getName().equals(mainThreadName)); + System.out.println("testObserveOnImmediateSchedulerByDefaultForSemaphoreIsolation: " + subscribeThread.get() + " => " + mainThreadName); + assertTrue(subscribeThread.get().getName().equals(mainThreadName)); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertSaneHystrixRequestLog(1); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertNull(command.getExecutionException()); + assertFalse(command.isResponseFromFallback()); + } + + /** + * Test that the circuit-breaker will 'trip' and prevent command execution on subsequent calls. + */ + @Test + public void testCircuitBreakerTripsAfterFailures() throws InterruptedException { + HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("KnownFailureTestCommandWithFallback"); + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(commandKey); + /* fail 3 times and then it should trip the circuit and stop executing */ + // failure 1 + KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); + attempt1.observe().toBlocking().single(); + Thread.sleep(100); + assertTrue(attempt1.isResponseFromFallback()); + assertFalse(attempt1.isCircuitBreakerOpen()); + assertFalse(attempt1.isResponseShortCircuited()); + + // failure 2 + KnownFailureTestCommandWithFallback attempt2 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); + attempt2.observe().toBlocking().single(); + Thread.sleep(100); + assertTrue(attempt2.isResponseFromFallback()); + assertFalse(attempt2.isCircuitBreakerOpen()); + assertFalse(attempt2.isResponseShortCircuited()); + + // failure 3 + KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); + attempt3.observe().toBlocking().single(); + Thread.sleep(100); + assertTrue(attempt3.isResponseFromFallback()); + assertFalse(attempt3.isResponseShortCircuited()); + // it should now be 'open' and prevent further executions + assertTrue(attempt3.isCircuitBreakerOpen()); + + // attempt 4 + KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); + attempt4.observe().toBlocking().single(); + Thread.sleep(100); + assertTrue(attempt4.isResponseFromFallback()); + // this should now be true as the response will be short-circuited + assertTrue(attempt4.isResponseShortCircuited()); + // this should remain open + assertTrue(attempt4.isCircuitBreakerOpen()); + + assertCommandExecutionEvents(attempt1, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(attempt2, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(attempt3, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(attempt4, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Test that the circuit-breaker being disabled doesn't wreak havoc. + */ + @Test + public void testExecutionSuccessWithCircuitBreakerDisabled() { + TestHystrixObservableCommand command = new TestCommandWithoutCircuitBreaker(); + try { + assertEquals(true, command.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertNull(command.getExecutionException()); + } + + /** + * Test a command execution timeout where the command didn't implement getFallback. + */ + @Test + public void testExecutionTimeoutWithNoFallbackUsingSemaphoreIsolation() { + TestHystrixObservableCommand command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 100); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be HystrixRuntimeException"); + } + } + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + + assertTrue(command.isResponseTimedOut()); + assertFalse(command.isResponseFromFallback()); + assertFalse(command.isResponseRejected()); + assertNotNull(command.getExecutionException()); + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution timeout where the command implemented getFallback. + */ + @Test + public void testExecutionTimeoutWithFallbackUsingSemaphoreIsolation() { + TestHystrixObservableCommand command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50); + try { + assertEquals(FlexibleTestHystrixObservableCommand.FALLBACK_VALUE, command.observe().toBlocking().single()); + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertTrue(command.isResponseTimedOut()); + assertTrue(command.isResponseFromFallback()); + assertNotNull(command.getExecutionException()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution timeout where the command implemented getFallback but it fails. + */ + @Test + public void testExecutionTimeoutFallbackFailureUsingSemaphoreIsolation() { + TestHystrixObservableCommand command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, 200); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (Exception e) { + if (e instanceof HystrixRuntimeException) { + e.printStackTrace(); + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertFalse(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + assertNotNull(command.getExecutionException()); + } else { + fail("the exception should be HystrixRuntimeException"); + } + } + // the time should be 200+ since we timeout at 200ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 200); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a semaphore command execution timeout where the command didn't implement getFallback. + */ + @Test + public void testSemaphoreExecutionTimeoutWithNoFallback() { + testExecutionTimeoutWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE); + } + + /** + * Test a thread command execution timeout where the command didn't implement getFallback. + */ + @Test + public void testThreadExecutionTimeoutWithNoFallback() { + testExecutionTimeoutWithNoFallback(ExecutionIsolationStrategy.THREAD); + } + + private void testExecutionTimeoutWithNoFallback(ExecutionIsolationStrategy isolationStrategy) { + TestHystrixObservableCommand command = getCommand(isolationStrategy, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (Exception e) { + e.printStackTrace(); + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be HystrixRuntimeException"); + } + } + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + + assertTrue(command.isResponseTimedOut()); + assertFalse(command.isResponseFromFallback()); + assertFalse(command.isResponseRejected()); + assertNotNull(command.getExecutionException()); + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } + + /** + * Test a semaphore command execution timeout where the command implemented getFallback. + */ + @Test + public void testSemaphoreIsolatedExecutionTimeoutWithSuccessfulFallback() { + testExecutionTimeoutWithSuccessfulFallback(ExecutionIsolationStrategy.SEMAPHORE); + } + + /** + * Test a thread command execution timeout where the command implemented getFallback. + */ + @Test + public void testThreadIsolatedExecutionTimeoutWithSuccessfulFallback() { + testExecutionTimeoutWithSuccessfulFallback(ExecutionIsolationStrategy.THREAD); + } + + private void testExecutionTimeoutWithSuccessfulFallback(ExecutionIsolationStrategy isolationStrategy) { + TestHystrixObservableCommand command = getCommand(isolationStrategy, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 100); + try { + assertEquals(FlexibleTestHystrixObservableCommand.FALLBACK_VALUE, command.observe().toBlocking().single()); + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertTrue(command.isResponseTimedOut()); + assertTrue(command.isResponseFromFallback()); + assertNotNull(command.getExecutionException()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } + + /** + * Test a semaphore command execution timeout where the command implemented getFallback but it fails synchronously. + */ + @Test + public void testSemaphoreExecutionTimeoutSyncFallbackFailure() { + testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, false); + } + + /** + * Test a semaphore command execution timeout where the command implemented getFallback but it fails asynchronously. + */ + @Test + public void testSemaphoreExecutionTimeoutAsyncFallbackFailure() { + testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, true); + } + + /** + * Test a thread command execution timeout where the command implemented getFallback but it fails synchronously. + */ + @Test + public void testThreadExecutionTimeoutSyncFallbackFailure() { + testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy.THREAD, false); + } + + /** + * Test a thread command execution timeout where the command implemented getFallback but it fails asynchronously. + */ + @Test + public void testThreadExecutionTimeoutAsyncFallbackFailure() { + testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy.THREAD, true); + } + private void testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy isolationStrategy, boolean asyncFallbackException) { + TestHystrixObservableCommand command = getCommand(isolationStrategy, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.FAILURE, 100); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (Exception e) { + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertFalse(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + assertNotNull(command.getExecutionException()); + } else { + fail("the exception should be HystrixRuntimeException"); + } + } + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); + } + + /** + * Test that the circuit-breaker counts a command execution timeout as a 'timeout' and not just failure. + */ + @Test + public void testShortCircuitFallbackCounter() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + KnownFailureTestCommandWithFallback command1 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); + KnownFailureTestCommandWithFallback command2 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); + try { + command1.observe().toBlocking().single(); + command2.observe().toBlocking().single(); + + // will be -1 because it never attempted execution + assertEquals(-1, command2.getExecutionTimeInMilliseconds()); + assertTrue(command2.isResponseShortCircuited()); + assertFalse(command2.isResponseTimedOut()); + assertNotNull(command2.getExecutionException()); + // semaphore isolated + assertFalse(command2.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertCommandExecutionEvents(command1, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertSaneHystrixRequestLog(2); + } + + @Test + public void testExecutionSemaphoreWithObserve() { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + TestSemaphoreCommand command1 = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + + // single thread should work + try { + boolean result = command1.observe().toBlocking().toFuture().get(); + assertTrue(result); + } catch (Exception e) { + // we shouldn't fail on this one + throw new RuntimeException(e); + } + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + final TryableSemaphoreActual semaphore = + new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); + + final TestSemaphoreCommand command2 = new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + command2.observe().toBlocking().toFuture().get(); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + final TestSemaphoreCommand command3 = new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + Runnable r3 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + command3.observe().toBlocking().toFuture().get(); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore + Thread t2 = new Thread(r2); + Thread t3 = new Thread(r3); + + t2.start(); + try { + Thread.sleep(100); + } catch (Throwable ex) { + fail(ex.getMessage()); + } + + t3.start(); + try { + t2.join(); + t3.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + if (!exceptionReceived.get()) { + fail("We expected an exception on the 2nd get"); + } + + System.out.println("CMD1 : " + command1.getExecutionEvents()); + System.out.println("CMD2 : " + command2.getExecutionEvents()); + System.out.println("CMD3 : " + command3.getExecutionEvents()); + assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + @Test + public void testRejectedExecutionSemaphoreWithFallback() { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + final ArrayBlockingQueue results = new ArrayBlockingQueue(2); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + final TestSemaphoreCommandWithFallback command1 = new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false); + Runnable r1 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(command1.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + final TestSemaphoreCommandWithFallback command2 = new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false); + Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(command2.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore and return fallback + Thread t1 = new Thread(r1); + Thread t2 = new Thread(r2); + + t1.start(); + try { + //give t1 a headstart + Thread.sleep(50); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + t2.start(); + try { + t1.join(); + t2.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + if (exceptionReceived.get()) { + fail("We should have received a fallback response"); + } + + // both threads should have returned values + assertEquals(2, results.size()); + // should contain both a true and false result + assertTrue(results.contains(Boolean.TRUE)); + assertTrue(results.contains(Boolean.FALSE)); + + assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command1.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + @Test + public void testSemaphorePermitsInUse() { + // this semaphore will be shared across multiple command instances + final TryableSemaphoreActual sharedSemaphore = + new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(3)); + + // creates thread using isolated semaphore + final TryableSemaphoreActual isolatedSemaphore = + new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); + + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + + //used to wait until all commands are started + final CountDownLatch startLatch = new CountDownLatch((sharedSemaphore.numberOfPermits.get()) * 2 + 1); + + // used to signal that all command can finish + final CountDownLatch sharedLatch = new CountDownLatch(1); + final CountDownLatch isolatedLatch = new CountDownLatch(1); + + final List> commands = new ArrayList>(); + final List> results = new ArrayList>(); + + HystrixObservableCommand isolated = new LatchedSemaphoreCommand("ObservableCommand-Isolated", circuitBreaker, isolatedSemaphore, startLatch, isolatedLatch); + commands.add(isolated); + + for (int s = 0; s < sharedSemaphore.numberOfPermits.get() * 2; s++) { + HystrixObservableCommand shared = new LatchedSemaphoreCommand("ObservableCommand-Shared", circuitBreaker, sharedSemaphore, startLatch, sharedLatch); + commands.add(shared); + Observable result = shared.toObservable(); + results.add(result); + } + + Observable isolatedResult = isolated.toObservable(); + results.add(isolatedResult); + + // verifies no permits in use before starting commands + assertEquals("before commands start, shared semaphore should be unused", 0, sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("before commands start, isolated semaphore should be unused", 0, isolatedSemaphore.getNumberOfPermitsUsed()); + + final CountDownLatch allTerminal = new CountDownLatch(1); + + Observable.merge(results) + .subscribeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(Thread.currentThread().getName() + " OnCompleted"); + allTerminal.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread().getName() + " OnError : " + e); + allTerminal.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(Thread.currentThread().getName() + " OnNext : " + b); + } + }); + + try { + assertTrue(startLatch.await(20, TimeUnit.SECONDS)); + } catch (Throwable ex) { + fail(ex.getMessage()); + } + + // verifies that all semaphores are in use + assertEquals("immediately after command start, all shared semaphores should be in-use", + sharedSemaphore.numberOfPermits.get().longValue(), sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("immediately after command start, isolated semaphore should be in-use", + isolatedSemaphore.numberOfPermits.get().longValue(), isolatedSemaphore.getNumberOfPermitsUsed()); + + // signals commands to finish + sharedLatch.countDown(); + isolatedLatch.countDown(); + + try { + assertTrue(allTerminal.await(5000, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on commands"); + } + + // verifies no permits in use after finishing threads + assertEquals("after all threads have finished, no shared semaphores should be in-use", 0, sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("after all threads have finished, isolated semaphore not in-use", 0, isolatedSemaphore.getNumberOfPermitsUsed()); + + // verifies that some executions failed + int numSemaphoreRejected = 0; + for (HystrixObservableCommand cmd: commands) { + if (cmd.isResponseSemaphoreRejected()) { + numSemaphoreRejected++; + } + } + assertEquals("expected some of shared semaphore commands to get rejected", sharedSemaphore.numberOfPermits.get().longValue(), numSemaphoreRejected); + } + + /** + * Test that HystrixOwner can be passed in dynamically. + */ + @Test + public void testDynamicOwner() { + try { + TestHystrixObservableCommand command = new DynamicOwnerTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE); + assertEquals(true, command.observe().toBlocking().single()); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test a successful command execution. + */ + @Test + public void testDynamicOwnerFails() { + try { + TestHystrixObservableCommand command = new DynamicOwnerTestCommand(null); + assertEquals(true, command.observe().toBlocking().single()); + fail("we should have thrown an exception as we need an owner"); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + + assertEquals(0, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } catch (Exception e) { + // success if we get here + } + } + + /** + * Test that HystrixCommandKey can be passed in dynamically. + */ + @Test + public void testDynamicKey() { + try { + DynamicOwnerAndKeyTestCommand command1 = new DynamicOwnerAndKeyTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE, InspectableBuilder.CommandKeyForUnitTest.KEY_ONE); + assertEquals(true, command1.observe().toBlocking().single()); + DynamicOwnerAndKeyTestCommand command2 = new DynamicOwnerAndKeyTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE, InspectableBuilder.CommandKeyForUnitTest.KEY_TWO); + assertEquals(true, command2.observe().toBlocking().single()); + + // 2 different circuit breakers should be created + assertNotSame(command1.getCircuitBreaker(), command2.getCircuitBreaker()); + + // semaphore isolated + assertFalse(command1.isExecutedInThread()); + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future + */ + @Test + public void testRequestCache1UsingThreadIsolation() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("A", f2.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // the second one should not have executed as it should have received the cached value instead + assertFalse(command2.executed); + + assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertTrue(command1.getExecutionTimeInMilliseconds() > -1); + assertFalse(command1.isResponseFromCache()); + assertNull(command1.getExecutionException()); + + // the execution log for command2 should show it came from cache + assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertTrue(command2.getExecutionTimeInMilliseconds() == -1); + assertTrue(command2.isResponseFromCache()); + assertNull(command2.getExecutionException()); + + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Test Request scoped caching doesn't prevent different ones from executing + */ + @Test + public void testRequestCache2UsingThreadIsolation() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "B"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + + assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertTrue(command2.getExecutionTimeInMilliseconds() > -1); + assertFalse(command2.isResponseFromCache()); + + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testRequestCache3UsingThreadIsolation() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "B"); + SuccessfulCacheableCommand command3 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // but the 3rd should come from cache + assertFalse(command3.executed); + + assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertTrue(command3.getExecutionTimeInMilliseconds() == -1); + assertTrue(command3.isResponseFromCache()); + + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + /** + * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future + */ + @Test + public void testRequestCacheWithSlowExecution() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SlowCacheableCommand command1 = new SlowCacheableCommand(circuitBreaker, "A", 200); + SlowCacheableCommand command2 = new SlowCacheableCommand(circuitBreaker, "A", 100); + SlowCacheableCommand command3 = new SlowCacheableCommand(circuitBreaker, "A", 100); + SlowCacheableCommand command4 = new SlowCacheableCommand(circuitBreaker, "A", 100); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + Future f4 = command4.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f2.get()); + assertEquals("A", f3.get()); + assertEquals("A", f4.get()); + + assertEquals("A", f1.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // the second one should not have executed as it should have received the cached value instead + assertFalse(command2.executed); + assertFalse(command3.executed); + assertFalse(command4.executed); + + // the execution log for command1 should show an EMIT and a SUCCESS + assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertTrue(command1.getExecutionTimeInMilliseconds() > -1); + assertFalse(command1.isResponseFromCache()); + + // the execution log for command2 should show it came from cache + assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertTrue(command2.getExecutionTimeInMilliseconds() == -1); + assertTrue(command2.isResponseFromCache()); + + assertCommandExecutionEvents(command3, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertTrue(command3.isResponseFromCache()); + assertTrue(command3.getExecutionTimeInMilliseconds() == -1); + + assertCommandExecutionEvents(command4, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertTrue(command4.isResponseFromCache()); + assertTrue(command4.getExecutionTimeInMilliseconds() == -1); + + assertSaneHystrixRequestLog(4); + + // semaphore isolated + assertFalse(command1.isExecutedInThread()); + assertFalse(command2.isExecutedInThread()); + assertFalse(command3.isExecutedInThread()); + assertFalse(command4.isExecutedInThread()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testNoRequestCache3UsingThreadIsolation() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, false, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, false, "B"); + SuccessfulCacheableCommand command3 = new SuccessfulCacheableCommand(circuitBreaker, false, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // this should also execute since we disabled the cache + assertTrue(command3.executed); + + assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + + // thread isolated + assertTrue(command1.isExecutedInThread()); + assertTrue(command2.isExecutedInThread()); + assertTrue(command3.isExecutedInThread()); + } + + @Test + public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + NoRequestCacheTimeoutWithoutFallback r1 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + System.out.println("r1 value: " + r1.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r1.isResponseTimedOut()); + // what we want + } + + NoRequestCacheTimeoutWithoutFallback r2 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r2.observe().toBlocking().single(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r2.isResponseTimedOut()); + // what we want + } + + NoRequestCacheTimeoutWithoutFallback r3 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + Future f3 = r3.observe().toBlocking().toFuture(); + try { + f3.get(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (ExecutionException e) { + e.printStackTrace(); + assertTrue(r3.isResponseTimedOut()); + // what we want + } + + Thread.sleep(500); // timeout on command is set to 200ms + + NoRequestCacheTimeoutWithoutFallback r4 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r4.observe().toBlocking().single(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertTrue(r4.isResponseTimedOut()); + assertFalse(r4.isResponseFromFallback()); + // what we want + } + + assertCommandExecutionEvents(r1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(4); + } + + @Test + public void testRequestCacheOnTimeoutCausesNullPointerException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + + RequestCacheNullPointerExceptionCase command1 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + RequestCacheNullPointerExceptionCase command2 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + RequestCacheNullPointerExceptionCase command3 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + RequestCacheNullPointerExceptionCase command4 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + RequestCacheNullPointerExceptionCase command5 = new RequestCacheNullPointerExceptionCase(circuitBreaker); + // Expect it to time out - all results should be false + assertFalse(command1.observe().toBlocking().single()); + assertFalse(command2.observe().toBlocking().single()); // return from cache #1 + assertFalse(command3.observe().toBlocking().single()); // return from cache #2 + Thread.sleep(500); // timeout on command is set to 200ms + Boolean value = command4.observe().toBlocking().single(); // return from cache #3 + assertFalse(value); + Future f = command5.observe().toBlocking().toFuture(); // return from cache #4 + // the bug is that we're getting a null Future back, rather than a Future that returns false + assertNotNull(f); + assertFalse(f.get()); + + assertTrue(command5.isResponseFromFallback()); + assertTrue(command5.isResponseTimedOut()); + assertFalse(command5.isFailedExecution()); + assertFalse(command5.isResponseShortCircuited()); + assertNotNull(command5.getExecutionException()); + + assertCommandExecutionEvents(command1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(command3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(command4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(command5, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); + + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(5); + } + + @Test + public void testRequestCacheOnTimeoutThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + RequestCacheTimeoutWithoutFallback r1 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + System.out.println("r1 value: " + r1.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r1.isResponseTimedOut()); + assertNotNull(r1.getExecutionException()); + // what we want + } + + RequestCacheTimeoutWithoutFallback r2 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r2.observe().toBlocking().single(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r2.isResponseTimedOut()); + assertNotNull(r2.getExecutionException()); + // what we want + } + + RequestCacheTimeoutWithoutFallback r3 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + Future f3 = r3.observe().toBlocking().toFuture(); + try { + f3.get(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (ExecutionException e) { + e.printStackTrace(); + assertTrue(r3.isResponseTimedOut()); + assertNotNull(r3.getExecutionException()); + // what we want + } + + Thread.sleep(500); // timeout on command is set to 200ms + + RequestCacheTimeoutWithoutFallback r4 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r4.observe().toBlocking().single(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r4.isResponseTimedOut()); + assertFalse(r4.isResponseFromFallback()); + assertNotNull(r4.getExecutionException()); + } + + assertCommandExecutionEvents(r1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(r3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(r4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(4); + } + + @Test + public void testRequestCacheOnThreadRejectionThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CountDownLatch completionLatch = new CountDownLatch(1); + RequestCacheThreadRejectionWithoutFallback r1 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r1: " + r1.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertTrue(r1.isResponseRejected()); + assertNotNull(r1.getExecutionException()); + // what we want + } + + RequestCacheThreadRejectionWithoutFallback r2 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r2: " + r2.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + // e.printStackTrace(); + assertTrue(r2.isResponseRejected()); + assertNotNull(r2.getExecutionException()); + // what we want + } + + RequestCacheThreadRejectionWithoutFallback r3 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("f3: " + r3.observe().toBlocking().toFuture().get()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (ExecutionException e) { + assertTrue(r3.isResponseRejected()); + assertTrue(e.getCause() instanceof HystrixRuntimeException); + assertNotNull(r3.getExecutionException()); + } + + // let the command finish (only 1 should actually be blocked on this due to the response cache) + completionLatch.countDown(); + + // then another after the command has completed + RequestCacheThreadRejectionWithoutFallback r4 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r4: " + r4.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + // e.printStackTrace(); + assertTrue(r4.isResponseRejected()); + assertFalse(r4.isResponseFromFallback()); + assertNotNull(r4.getExecutionException()); + // what we want + } + + assertCommandExecutionEvents(r1, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING); + assertCommandExecutionEvents(r2, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(r3, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertCommandExecutionEvents(r4, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(4); + } + + /** + * Test that we can do basic execution without a RequestVariable being initialized. + */ + @Test + public void testBasicExecutionWorksWithoutRequestVariable() { + try { + /* force the RequestVariable to not be initialized */ + HystrixRequestContext.setContextOnCurrentThread(null); + + TestHystrixObservableCommand command = new SuccessfulTestCommand(ExecutionIsolationStrategy.SEMAPHORE); + assertEquals(true, command.observe().toBlocking().single()); + + TestHystrixObservableCommand command2 = new SuccessfulTestCommand(ExecutionIsolationStrategy.SEMAPHORE); + assertEquals(true, command2.observe().toBlocking().toFuture().get()); + + // we should be able to execute without a RequestVariable if ... + // 1) We don't have a cacheKey + // 2) We don't ask for the RequestLog + // 3) We don't do collapsing + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + assertNull(command.getExecutionException()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception => " + e.getMessage()); + } + + assertNull(HystrixRequestLog.getCurrentRequest()); + } + + /** + * Test that if we try and execute a command with a cacheKey without initializing RequestVariable that it gives an error. + */ + @Test + public void testCacheKeyExecutionRequiresRequestVariable() { + try { + /* force the RequestVariable to not be initialized */ + HystrixRequestContext.setContextOnCurrentThread(null); + + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + + SuccessfulCacheableCommand command = new SuccessfulCacheableCommand(circuitBreaker, true, "one"); + assertEquals("one", command.observe().toBlocking().single()); + + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "two"); + assertEquals("two", command2.observe().toBlocking().toFuture().get()); + + fail("We expect an exception because cacheKey requires RequestVariable."); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + assertNull(command.getExecutionException()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Test that a BadRequestException can be synchronously thrown from a semaphore-isolated command and not count towards errors and bypasses fallback. + */ + @Test + public void testSemaphoreIsolatedBadRequestSyncExceptionObserve() { + testBadRequestExceptionObserve(ExecutionIsolationStrategy.SEMAPHORE, KnownHystrixBadRequestFailureTestCommand.SYNC_EXCEPTION); + } + + /** + * Test that a BadRequestException can be asynchronously thrown from a semaphore-isolated command and not count towards errors and bypasses fallback. + */ + @Test + public void testSemaphoreIsolatedBadRequestAsyncExceptionObserve() { + testBadRequestExceptionObserve(ExecutionIsolationStrategy.SEMAPHORE, KnownHystrixBadRequestFailureTestCommand.ASYNC_EXCEPTION); + } + + /** + * Test that a BadRequestException can be synchronously thrown from a thread-isolated command and not count towards errors and bypasses fallback. + */ + @Test + public void testThreadIsolatedBadRequestSyncExceptionObserve() { + testBadRequestExceptionObserve(ExecutionIsolationStrategy.THREAD, KnownHystrixBadRequestFailureTestCommand.SYNC_EXCEPTION); + } + + /** + * Test that a BadRequestException can be asynchronously thrown from a thread-isolated command and not count towards errors and bypasses fallback. + */ + @Test + public void testThreadIsolatedBadRequestAsyncExceptionObserve() { + testBadRequestExceptionObserve(ExecutionIsolationStrategy.THREAD, KnownHystrixBadRequestFailureTestCommand.ASYNC_EXCEPTION); + } + + private void testBadRequestExceptionObserve(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + KnownHystrixBadRequestFailureTestCommand command1 = new KnownHystrixBadRequestFailureTestCommand(circuitBreaker, isolationStrategy, asyncException); + try { + command1.observe().toBlocking().single(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (HystrixBadRequestException e) { + // success + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + + assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST); + assertSaneHystrixRequestLog(1); + assertNotNull(command1.getExecutionException()); + } + + /** + * Test that synchronous BadRequestException behavior works the same on a cached response for a semaphore-isolated command. + */ + @Test + public void testSyncBadRequestExceptionOnResponseFromCacheInSempahore() { + testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy.SEMAPHORE, KnownHystrixBadRequestFailureTestCommand.SYNC_EXCEPTION); + } + + /** + * Test that asynchronous BadRequestException behavior works the same on a cached response for a semaphore-isolated command. + */ + @Test + public void testAsyncBadRequestExceptionOnResponseFromCacheInSemaphore() { + testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy.SEMAPHORE, KnownHystrixBadRequestFailureTestCommand.ASYNC_EXCEPTION); + } + + /** + * Test that synchronous BadRequestException behavior works the same on a cached response for a thread-isolated command. + */ + @Test + public void testSyncBadRequestExceptionOnResponseFromCacheInThread() { + testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy.THREAD, KnownHystrixBadRequestFailureTestCommand.SYNC_EXCEPTION); + } + + /** + * Test that asynchronous BadRequestException behavior works the same on a cached response for a thread-isolated command. + */ + @Test + public void testAsyncBadRequestExceptionOnResponseFromCacheInThread() { + testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy.THREAD, KnownHystrixBadRequestFailureTestCommand.ASYNC_EXCEPTION); + } + + private void testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + + KnownHystrixBadRequestFailureTestCommand command1 = new KnownHystrixBadRequestFailureTestCommand(circuitBreaker, isolationStrategy, asyncException); + // execute once to cache the value + try { + command1.observe().toBlocking().single(); + } catch (Throwable e) { + // ignore + } + + KnownHystrixBadRequestFailureTestCommand command2 = new KnownHystrixBadRequestFailureTestCommand(circuitBreaker, isolationStrategy, asyncException); + try { + command2.observe().toBlocking().toFuture().get(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (ExecutionException e) { + e.printStackTrace(); + if (e.getCause() instanceof HystrixBadRequestException) { + // success + } else { + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + + assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST); + assertCommandExecutionEvents(command2, HystrixEventType.BAD_REQUEST); + assertSaneHystrixRequestLog(2); + assertNotNull(command1.getExecutionException()); + assertNotNull(command2.getExecutionException()); + } + + /** + * Test a checked Exception being thrown + */ + @Test + public void testCheckedExceptionViaExecute() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker); + try { + command.observe().toBlocking().single(); + fail("we expect to receive a " + Exception.class.getSimpleName()); + } catch (Exception e) { + assertEquals("simulated checked exception message", e.getCause().getMessage()); + } + + assertEquals("simulated checked exception message", command.getFailedExecutionException().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertNotNull(command.getExecutionException()); + + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a java.lang.Error being thrown + * + * @throws InterruptedException + */ + @Test + public void testCheckedExceptionViaObserve() throws InterruptedException { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker); + final AtomicReference t = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + try { + command.observe().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + t.set(e); + latch.countDown(); + } + + @Override + public void onNext(Boolean args) { + + } + + }); + } catch (Exception e) { + e.printStackTrace(); + fail("we should not get anything thrown, it should be emitted via the Observer#onError method"); + } + + latch.await(1, TimeUnit.SECONDS); + assertNotNull(t.get()); + t.get().printStackTrace(); + + assertTrue(t.get() instanceof HystrixRuntimeException); + assertEquals("simulated checked exception message", t.get().getCause().getMessage()); + assertEquals("simulated checked exception message", command.getFailedExecutionException().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + // semaphore isolated + assertFalse(command.isExecutedInThread()); + assertSaneHystrixRequestLog(1); + } + + /** + * Test a java.lang.Error being thrown + * + * @throws InterruptedException + */ + @Test + public void testErrorThrownViaObserve() throws InterruptedException { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithErrorThrown command = new CommandWithErrorThrown(circuitBreaker, true); + final AtomicReference t = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + try { + command.observe().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + t.set(e); + latch.countDown(); + } + + @Override + public void onNext(Boolean args) { + + } + + }); + } catch (Exception e) { + e.printStackTrace(); + fail("we should not get anything thrown, it should be emitted via the Observer#onError method"); + } + + latch.await(1, TimeUnit.SECONDS); + assertNotNull(t.get()); + t.get().printStackTrace(); + + assertTrue(t.get() instanceof HystrixRuntimeException); + // the actual error is an extra cause level deep because Hystrix needs to wrap Throwable/Error as it's public + // methods only support Exception and it's not a strong enough reason to break backwards compatibility and jump to version 2.x + assertEquals("simulated java.lang.Error message", t.get().getCause().getCause().getMessage()); + assertEquals("simulated java.lang.Error message", command.getFailedExecutionException().getCause().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertNotNull(command.getExecutionException()); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertFalse(command.isExecutedInThread()); + assertSaneHystrixRequestLog(1); + } + + @Test + public void testInterruptObserveOnTimeout() throws InterruptedException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true); + + // when + cmd.observe().subscribe(); + + // then + Thread.sleep(500); + assertTrue(cmd.hasBeenInterrupted()); + } + + @Test + public void testInterruptToObservableOnTimeout() throws InterruptedException { + // given + InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true); + + // when + cmd.toObservable().subscribe(); + + // then + Thread.sleep(500); + assertTrue(cmd.hasBeenInterrupted()); + } + + + + @Override + protected void assertHooksOnSuccess(Func0> ctor, Action1> assertion) { + assertBlockingObserve(ctor.call(), assertion, true); + assertNonBlockingObserve(ctor.call(), assertion, true); + } + + @Override + protected void assertHooksOnFailure(Func0> ctor, Action1> assertion) { + assertBlockingObserve(ctor.call(), assertion, false); + assertNonBlockingObserve(ctor.call(), assertion, false); + } + + @Override + void assertHooksOnFailure(Func0> ctor, Action1> assertion, boolean failFast) { + + } + + /** + *********************** HystrixObservableCommand-specific THREAD-ISOLATED Execution Hook Tests ************************************** + */ + + /** + * Short-circuitInteger : NO + * Thread/semaphore: THREAD + * Thread Pool fullInteger : NO + * Thread Pool Queue fullInteger: NO + * Timeout: NO + * Execution Result: EMITx4, SUCCESS + */ + @Test + public void testExecutionHookThreadMultipleEmitsAndThenSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.MULTIPLE_EMITS_THEN_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(4, 0, 1)); + assertTrue(hook.executionEventsMatch(4, 0, 1)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionSuccess - onThreadComplete - onSuccess - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuitInteger : NO + * Thread/semaphore: THREAD + * Thread Pool fullInteger : NO + * Thread Pool Queue fullInteger: NO + * Timeout: NO + * Execution Result: EMITx4, FAILURE, FALLBACK_EMITx4, FALLBACK_SUCCESS + */ + @Test + public void testExecutionHookThreadMultipleEmitsThenErrorThenMultipleFallbackEmitsAndThenFallbackSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.MULTIPLE_EMITS_THEN_FAILURE, 0, AbstractTestHystrixCommand.FallbackResult.MULTIPLE_EMITS_THEN_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(8, 0, 1)); + assertTrue(hook.executionEventsMatch(4, 1, 0)); + assertTrue(hook.fallbackEventsMatch(4, 0, 1)); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - " + + "onExecutionEmit - !onRunSuccess - !onComplete - onEmit - " + + "onExecutionEmit - !onRunSuccess - !onComplete - onEmit - " + + "onExecutionEmit - !onRunSuccess - !onComplete - onEmit - " + + "onExecutionEmit - !onRunSuccess - !onComplete - onEmit - " + + "onExecutionError - !onRunError - onThreadComplete - onFallbackStart - " + + "onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - " + + "onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - " + + "onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - " + + "onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - " + + "onFallbackSuccess - onSuccess - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuitInteger : NO + * Thread/semaphore: THREAD + * Thread Pool fullInteger : NO + * Thread Pool Queue fullInteger: NO + * Timeout: NO + * Execution Result: asynchronous HystrixBadRequestException + */ + @Test + public void testExecutionHookThreadAsyncBadRequestException() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_BAD_REQUEST); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(HystrixBadRequestException.class, hook.getCommandException().getClass()); + assertEquals(HystrixBadRequestException.class, hook.getExecutionException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onError - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuitInteger : NO + * Thread/semaphore: THREAD + * Thread Pool fullInteger : NO + * Thread Pool Queue fullInteger: NO + * Timeout: NO + * Execution Result: async HystrixRuntimeException + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadAsyncExceptionNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 0, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertNull(hook.getFallbackException()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onError - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuitInteger : NO + * Thread/semaphore: THREAD + * Thread Pool fullInteger : NO + * Thread Pool Queue fullInteger: NO + * Timeout: NO + * Execution Result: async HystrixRuntimeException + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadAsyncExceptionSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(1, 0, 1)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(1, 0, 1)); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuitInteger : NO + * Thread/semaphore: THREAD + * Thread Pool fullInteger : NO + * Thread Pool Queue fullInteger: NO + * Timeout: NO + * Execution Result: sync HystrixRuntimeException + * Fallback: async HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadSyncExceptionAsyncUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuitInteger : NO + * Thread/semaphore: THREAD + * Thread Pool fullInteger : NO + * Thread Pool Queue fullInteger: NO + * Timeout: NO + * Execution Result: async HystrixRuntimeException + * Fallback: sync HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadAsyncExceptionSyncUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE, AbstractTestHystrixCommand.FallbackResult.FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuitInteger : NO + * Thread/semaphore: THREAD + * Thread Pool fullInteger : NO + * Thread Pool Queue fullInteger: NO + * Timeout: NO + * Execution Result: async HystrixRuntimeException + * Fallback: async HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadAsyncExceptionAsyncUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE, AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuitInteger : YES + * Thread/semaphore: THREAD + * Fallback: async HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadShortCircuitAsyncUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCircuitOpenCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 0, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + *********************** END HystrixObservableCommand-specific THREAD-ISOLATED Execution Hook Tests ************************************** + */ + + /** + ********************* HystrixObservableCommand-specific SEMAPHORE-ISOLATED Execution Hook Tests *********************************** + */ + + /** + * Short-circuitInteger : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reachedInteger : NO + * Execution Result: HystrixRuntimeException + * Fallback: asynchronous HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreExceptionUnsuccessfulAsynchronousFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixObservableCommand call() { + return getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixObservableCommand command) { + TestableExecutionHook hook = command.getBuilder().executionHook; + assertTrue(hook.commandEmissionsMatch(0, 1, 0)); + assertTrue(hook.executionEventsMatch(0, 1, 0)); + assertTrue(hook.fallbackEventsMatch(0, 1, 0)); + assertEquals(RuntimeException.class, hook.getCommandException().getClass()); + assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); + assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); + assertEquals("onStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); + } + }); + } + + /** + ********************* END HystrixObservableCommand-specific SEMAPHORE-ISOLATED Execution Hook Tests *********************************** + */ + + /** + * Test a command execution that fails but has a fallback. + */ + @Test + public void testExecutionFailureWithFallbackImplementedButDisabled() { + TestHystrixObservableCommand commandEnabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), true, true); + try { + assertEquals(false, commandEnabled.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + TestHystrixObservableCommand commandDisabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), false, true); + try { + assertEquals(false, commandDisabled.observe().toBlocking().single()); + fail("expect exception thrown"); + } catch (Exception e) { + // expected + } + + assertEquals("we failed with a simulated issue", commandDisabled.getFailedExecutionException().getMessage()); + + assertTrue(commandDisabled.isFailedExecution()); + assertNotNull(commandDisabled.getExecutionException()); + + assertCommandExecutionEvents(commandEnabled, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertCommandExecutionEvents(commandDisabled, HystrixEventType.FAILURE); + assertEquals(0, commandDisabled.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + /** + * Test that we can still use thread isolation if desired. + */ + @Test + public void testSynchronousExecutionTimeoutValueViaExecute() { + HystrixObservableCommand.Setter properties = HystrixObservableCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) + .withExecutionTimeoutInMilliseconds(50)); + + System.out.println(">>>>> Begin: " + System.currentTimeMillis()); + + final AtomicBoolean startedExecution = new AtomicBoolean(); + HystrixObservableCommand command = new HystrixObservableCommand(properties) { + @Override + protected Observable construct() { + + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber t1) { + try { + startedExecution.set(true); + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + t1.onNext("hello"); + t1.onCompleted(); + } + + }); + } + + @Override + protected Observable resumeWithFallback() { + if (isResponseTimedOut()) { + return Observable.just("timed-out"); + } else { + return Observable.just("abc"); + } + } + }; + + System.out.println(">>>>> Start: " + System.currentTimeMillis()); + String value = command.observe().toBlocking().single(); + System.out.println(">>>>> End: " + System.currentTimeMillis()); + assertTrue(command.isResponseTimedOut()); + assertEquals("expected fallback value", "timed-out", value); + + // Thread isolated + assertTrue(!startedExecution.get() || command.isExecutedInThread()); + assertNotNull(command.getExecutionException()); + + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testSynchronousExecutionUsingThreadIsolationTimeoutValueViaObserve() { + HystrixObservableCommand.Setter properties = HystrixObservableCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) + .withExecutionTimeoutInMilliseconds(50)); + + final AtomicBoolean startedExecution = new AtomicBoolean(); + HystrixObservableCommand command = new HystrixObservableCommand(properties) { + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber t1) { + startedExecution.set(true); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + t1.onNext("hello"); + t1.onCompleted(); + } + + }); + } + + @Override + protected Observable resumeWithFallback() { + if (isResponseTimedOut()) { + return Observable.just("timed-out"); + } else { + return Observable.just("abc"); + } + } + }; + + String value = command.observe().toBlocking().last(); + assertTrue(command.isResponseTimedOut()); + assertEquals("expected fallback value", "timed-out", value); + + // Thread isolated + assertTrue(!startedExecution.get() || command.isExecutedInThread()); + assertNotNull(command.getExecutionException()); + + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testAsyncExecutionTimeoutValueViaObserve() { + HystrixObservableCommand.Setter properties = HystrixObservableCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionTimeoutInMilliseconds(50)); + + HystrixObservableCommand command = new HystrixObservableCommand(properties) { + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber t1) { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + System.out.println("********** interrupted on timeout"); + e.printStackTrace(); + } + // should never reach here + t1.onNext("hello"); + t1.onCompleted(); + } + }).subscribeOn(Schedulers.newThread()); + } + + @Override + protected Observable resumeWithFallback() { + if (isResponseTimedOut()) { + return Observable.just("timed-out"); + } else { + return Observable.just("abc"); + } + } + }; + + String value = command.observe().toBlocking().last(); + assertTrue(command.isResponseTimedOut()); + assertEquals("expected fallback value", "timed-out", value); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + assertNotNull(command.getExecutionException()); + + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * See https://github.com/Netflix/Hystrix/issues/212 + */ + @Test + public void testObservableTimeoutNoFallbackThreadContext() { + TestSubscriber ts = new TestSubscriber(); + + final AtomicReference onErrorThread = new AtomicReference(); + final AtomicBoolean isRequestContextInitialized = new AtomicBoolean(); + + TestHystrixObservableCommand command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 100); + command.toObservable().doOnError(new Action1() { + + @Override + public void call(Throwable t1) { + System.out.println("onError: " + t1); + System.out.println("onError Thread: " + Thread.currentThread()); + System.out.println("ThreadContext in onError: " + HystrixRequestContext.isCurrentThreadInitialized()); + onErrorThread.set(Thread.currentThread()); + isRequestContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + } + + }).subscribe(ts); + + ts.awaitTerminalEvent(); + + assertTrue(isRequestContextInitialized.get()); + assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); + + List errors = ts.getOnErrorEvents(); + assertEquals(1, errors.size()); + Throwable e = errors.get(0); + if (errors.get(0) instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be ExecutionException with cause as HystrixRuntimeException"); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isResponseTimedOut()); + assertNotNull(command.getExecutionException()); + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertFalse(command.isExecutedInThread()); + } + + /** + * See https://github.com/Netflix/Hystrix/issues/212 + */ + @Test + public void testObservableTimeoutFallbackThreadContext() { + TestSubscriber ts = new TestSubscriber(); + + final AtomicReference onErrorThread = new AtomicReference(); + final AtomicBoolean isRequestContextInitialized = new AtomicBoolean(); + + TestHystrixObservableCommand command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 100); + command.toObservable().doOnNext(new Action1() { + + @Override + public void call(Object t1) { + System.out.println("onNext: " + t1); + System.out.println("onNext Thread: " + Thread.currentThread()); + System.out.println("ThreadContext in onNext: " + HystrixRequestContext.isCurrentThreadInitialized()); + onErrorThread.set(Thread.currentThread()); + isRequestContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + } + + }).subscribe(ts); + + ts.awaitTerminalEvent(); + + System.out.println("events: " + ts.getOnNextEvents()); + + assertTrue(isRequestContextInitialized.get()); + assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); + + List onNexts = ts.getOnNextEvents(); + assertEquals(1, onNexts.size()); + //assertFalse( onNexts.get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isResponseTimedOut()); + assertNotNull(command.getExecutionException()); + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + assertFalse(command.isExecutedInThread()); + } + + @Test + public void testRejectedViaSemaphoreIsolation() { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + final ArrayBlockingQueue results = new ArrayBlockingQueue(2); + + final TryableSemaphoreActual semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); + + //used to wait until all commands have started + final CountDownLatch startLatch = new CountDownLatch(2); + + // used to signal that all command can finish + final CountDownLatch sharedLatch = new CountDownLatch(1); + + final LatchedSemaphoreCommand command1 = new LatchedSemaphoreCommand(circuitBreaker, semaphore, startLatch, sharedLatch); + final LatchedSemaphoreCommand command2 = new LatchedSemaphoreCommand(circuitBreaker, semaphore, startLatch, sharedLatch); + + Observable merged = Observable.merge(command1.toObservable(), command2.toObservable()) + .subscribeOn(Schedulers.newThread()); + + final CountDownLatch terminal = new CountDownLatch(1); + + merged.subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(Thread.currentThread().getName() + " OnCompleted"); + terminal.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread().getName() + " OnError : " + e); + terminal.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(Thread.currentThread().getName() + " OnNext : " + b); + results.offer(b); + } + }); + + try { + assertTrue(startLatch.await(1000, TimeUnit.MILLISECONDS)); + sharedLatch.countDown(); + assertTrue(terminal.await(1000, TimeUnit.MILLISECONDS)); + } catch (Throwable ex) { + ex.printStackTrace(); + fail(ex.getMessage()); + } + + // one thread should have returned values + assertEquals(2, results.size()); + //1 should have gotten the normal value, the other - the fallback + assertTrue(results.contains(Boolean.TRUE)); + assertTrue(results.contains(Boolean.FALSE)); + + System.out.println("REQ LOG : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + + assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(2); + } + + @Test + public void testRejectedViaThreadIsolation() throws InterruptedException { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + final ArrayBlockingQueue results = new ArrayBlockingQueue(10); + final List executionThreads = Collections.synchronizedList(new ArrayList(20)); + final List responseThreads = Collections.synchronizedList(new ArrayList(10)); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + final CountDownLatch scheduleLatch = new CountDownLatch(2); + final CountDownLatch successLatch = new CountDownLatch(1); + final AtomicInteger count = new AtomicInteger(); + final AtomicReference command1Ref = new AtomicReference(); + final AtomicReference command2Ref = new AtomicReference(); + final AtomicReference command3Ref = new AtomicReference(); + + Runnable r1 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + final boolean shouldExecute = count.incrementAndGet() < 3; + try { + executionThreads.add(Thread.currentThread()); + TestThreadIsolationWithSemaphoreSetSmallCommand command1 = new TestThreadIsolationWithSemaphoreSetSmallCommand(circuitBreaker, 2, new Action0() { + + @Override + public void call() { + // make sure it's deterministic and we put 2 threads into the pool before the 3rd is submitted + if (shouldExecute) { + try { + scheduleLatch.countDown(); + successLatch.await(); + } catch (InterruptedException e) { + } + } + } + + }); + command1Ref.set(command1); + results.add(command1.toObservable().map(new Func1() { + + @Override + public Boolean call(Boolean b) { + responseThreads.add(Thread.currentThread()); + return b; + } + + }).doAfterTerminate(new Action0() { + + @Override + public void call() { + if (!shouldExecute) { + // the final thread that shouldn't execute releases the latch once it has run + // so it is deterministic that the other two fill the thread pool until this one rejects + successLatch.countDown(); + } + } + + }).toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + }); + + Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + final boolean shouldExecute = count.incrementAndGet() < 3; + try { + executionThreads.add(Thread.currentThread()); + TestThreadIsolationWithSemaphoreSetSmallCommand command2 = new TestThreadIsolationWithSemaphoreSetSmallCommand(circuitBreaker, 2, new Action0() { + + @Override + public void call() { + // make sure it's deterministic and we put 2 threads into the pool before the 3rd is submitted + if (shouldExecute) { + try { + scheduleLatch.countDown(); + successLatch.await(); + } catch (InterruptedException e) { + } + } + } + + }); + command2Ref.set(command2); + results.add(command2.toObservable().map(new Func1() { + + @Override + public Boolean call(Boolean b) { + responseThreads.add(Thread.currentThread()); + return b; + } + + }).doAfterTerminate(new Action0() { + + @Override + public void call() { + if (!shouldExecute) { + // the final thread that shouldn't execute releases the latch once it has run + // so it is deterministic that the other two fill the thread pool until this one rejects + successLatch.countDown(); + } + } + + }).toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + }); + + Runnable r3 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + final boolean shouldExecute = count.incrementAndGet() < 3; + try { + executionThreads.add(Thread.currentThread()); + TestThreadIsolationWithSemaphoreSetSmallCommand command3 = new TestThreadIsolationWithSemaphoreSetSmallCommand(circuitBreaker, 2, new Action0() { + + @Override + public void call() { + // make sure it's deterministic and we put 2 threads into the pool before the 3rd is submitted + if (shouldExecute) { + try { + scheduleLatch.countDown(); + successLatch.await(); + } catch (InterruptedException e) { + } + } + } + + }); + command3Ref.set(command3); + results.add(command3.toObservable().map(new Func1() { + + @Override + public Boolean call(Boolean b) { + responseThreads.add(Thread.currentThread()); + return b; + } + + }).doAfterTerminate(new Action0() { + + @Override + public void call() { + if (!shouldExecute) { + // the final thread that shouldn't execute releases the latch once it has run + // so it is deterministic that the other two fill the thread pool until this one rejects + successLatch.countDown(); + } + } + + }).toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + }); + + // 2 threads, the second should be rejected by the semaphore and return fallback + Thread t1 = new Thread(r1); + Thread t2 = new Thread(r2); + Thread t3 = new Thread(r3); + + t1.start(); + t2.start(); + // wait for the previous 2 thread to be running before starting otherwise it can race + scheduleLatch.await(500, TimeUnit.MILLISECONDS); + t3.start(); + try { + t1.join(); + t2.join(); + t3.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + // we should have 2 of the 3 return results + assertEquals(2, results.size()); + // the other thread should have thrown an Exception + assertTrue(exceptionReceived.get()); + + assertCommandExecutionEvents(command1Ref.get(), HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command2Ref.get(), HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertCommandExecutionEvents(command3Ref.get(), HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(3); + } + + /* ******************************************************************************************************** */ + /* *************************************** Request Context Testing Below ********************************** */ + /* ******************************************************************************************************** */ + + private RequestContextTestResults testRequestContextOnSuccess(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onNext(true); + s.onCompleted(); + } + + }).subscribeOn(userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnNextEvents().size()); + assertTrue(results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + return results; + } + + private RequestContextTestResults testRequestContextOnGracefulFailure(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder(circuitBreaker) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onError(new RuntimeException("graceful onError")); + } + + }).subscribeOn(userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnErrorEvents().size()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + return results; + } + + private RequestContextTestResults testRequestContextOnBadFailure(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + throw new RuntimeException("bad onError"); + } + + }).subscribeOn(userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnErrorEvents().size()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + return results; + } + + private RequestContextTestResults testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + s.onError(new RuntimeException("onError")); + } + + }).subscribeOn(userScheduler); + } + + @Override + protected Observable resumeWithFallback() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onNext(false); + s.onCompleted(); + } + + }).subscribeOn(userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(0, results.ts.getOnErrorEvents().size()); + assertEquals(1, results.ts.getOnNextEvents().size()); + assertEquals(false, results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + return results; + } + + private RequestContextTestResults testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(isolation) + .withExecutionIsolationSemaphoreMaxConcurrentRequests(0)) + .setThreadPool(new HystrixThreadPool() { + + @Override + public ThreadPoolExecutor getExecutor() { + return null; + } + + @Override + public void markThreadExecution() { + + } + + @Override + public void markThreadCompletion() { + + } + + @Override + public void markThreadRejection() { + + } + + @Override + public boolean isQueueSpaceAvailable() { + // always return false so we reject everything + return false; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + @Override + public Scheduler getScheduler(Func0 shouldInterruptThread) { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread); + } + + })) { + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + s.onError(new RuntimeException("onError")); + } + + }).subscribeOn(userScheduler); + } + + @Override + protected Observable resumeWithFallback() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onNext(false); + s.onCompleted(); + } + + }).subscribeOn(userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(0, results.ts.getOnErrorEvents().size()); + assertEquals(1, results.ts.getOnNextEvents().size()); + assertEquals(false, results.ts.getOnNextEvents().get(0)); + + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isResponseRejected()); + + if (isolation == ExecutionIsolationStrategy.SEMAPHORE) { + assertCommandExecutionEvents(command, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + } else { + assertCommandExecutionEvents(command, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + } + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + return results; + } + + private RequestContextTestResults testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(isolation)) + .setCircuitBreaker(new TestCircuitBreaker().setForceShortCircuit(true))) { + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + s.onError(new RuntimeException("onError")); + } + + }).subscribeOn(userScheduler); + } + + @Override + protected Observable resumeWithFallback() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onNext(false); + s.onCompleted(); + } + + }).subscribeOn(userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(0, results.ts.getOnErrorEvents().size()); + assertEquals(1, results.ts.getOnNextEvents().size()); + assertEquals(false, results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() == -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isResponseShortCircuited()); + + assertCommandExecutionEvents(command, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + return results; + } + + private RequestContextTestResults testRequestContextOnTimeout(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation).withExecutionTimeoutInMilliseconds(50))) { + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // ignore the interrupted exception + } + } + + }).subscribeOn(userScheduler); + } + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnErrorEvents().size()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isResponseTimedOut()); + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + return results; + } + + private RequestContextTestResults testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixObservableCommand command = new TestHystrixObservableCommand(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation).withExecutionTimeoutInMilliseconds(50))) { + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // ignore the interrupted exception + } + } + + }).subscribeOn(userScheduler); + } + + @Override + protected Observable resumeWithFallback() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onNext(false); + s.onCompleted(); + } + + }).subscribeOn(userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + System.out.println("timeoutWithFallback notification: " + n + " " + Thread.currentThread()); + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Fallback => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnNextEvents().size()); + assertEquals(false, results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isResponseTimedOut()); + + assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + return results; + } + + private final class RequestContextTestResults { + volatile TestHystrixObservableCommand command; + final AtomicReference originThread = new AtomicReference(); + final AtomicBoolean isContextInitialized = new AtomicBoolean(); + TestSubscriber ts = new TestSubscriber(); + final AtomicBoolean isContextInitializedObserveOn = new AtomicBoolean(); + final AtomicReference observeOnThread = new AtomicReference(); + } + + /* *************************************** testSuccessfulRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testSuccessfulRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testSuccessfulRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testSuccessfulRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testSuccessfulRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testSuccessfulRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testSuccessfulRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testGracefulFailureRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testGracefulFailureRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testGracefulFailureRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testGracefulFailureRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testGracefulFailureRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testGracefulFailureRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testGracefulFailureRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testBadFailureRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testBadFailureRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testBadFailureRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testBadFailureRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testBadFailureRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testBadFailureRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testBadFailureRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testFailureWithFallbackRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testFailureWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testFailureWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testFailureWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testRejectionWithFallbackRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testRejectionWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // fallback is performed by the calling thread + + assertTrue(results.isContextInitializedObserveOn.get()); + System.out.println("results.observeOnThread.get(): " + results.observeOnThread.get() + " " + Thread.currentThread()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // rejected so we stay on calling thread + + // thread isolated, but rejected, so this is false + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testRejectionWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated, but rejected, so this is false + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testRejectionWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler for getFallback + + // thread isolated, but rejected, so this is false + assertFalse(results.command.isExecutedInThread()); + } + + /* *************************************** testShortCircuitedWithFallbackRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // fallback is performed by the calling thread + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // rejected so we stay on calling thread + + // thread isolated ... but rejected so not executed in a thread + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler from getFallback + + // thread isolated ... but rejected so not executed in a thread + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler from getFallback + + // thread isolated ... but rejected so not executed in a thread + assertFalse(results.command.isExecutedInThread()); + } + + /* *************************************** testTimeoutRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testTimeoutRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + + HystrixCircuitBreaker.Factory.reset(); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testTimeoutRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + + HystrixCircuitBreaker.Factory.reset(); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testTimeoutRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + + HystrixCircuitBreaker.Factory.reset(); + } + + + /* *************************************** testTimeoutWithFallbackRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread + //(this use case is a little odd as it should generally not be the case that we are "timing out" a synchronous observable on semaphore isolation) + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + + HystrixCircuitBreaker.Factory.reset(); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + + HystrixCircuitBreaker.Factory.reset(); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + + HystrixCircuitBreaker.Factory.reset(); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [HystrixTimer] + */ + @Test + public void testTimeoutWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread for fallback + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // fallback uses the timeout thread + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + + HystrixCircuitBreaker.Factory.reset(); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + + HystrixCircuitBreaker.Factory.reset(); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + + } + //HystrixCircuitBreaker.Factory.reset(); + } + + /** + * Test support of multiple onNext events. + */ + @Test + public void testExecutionSuccessWithMultipleEvents() { + try { + TestCommandWithMultipleValues command = new TestCommandWithMultipleValues(); + assertEquals(Arrays.asList(true, false, true), command.observe().toList().toBlocking().single()); + + assertEquals(null, command.getFailedExecutionException()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test behavior when some onNext are received and then a failure. + */ + @Test + public void testExecutionPartialSuccess() { + try { + TestPartialSuccess command = new TestPartialSuccess(); + TestSubscriber ts = new TestSubscriber(); + command.toObservable().subscribe(ts); + ts.awaitTerminalEvent(); + ts.assertReceivedOnNext(Arrays.asList(1, 2, 3)); + assertEquals(1, ts.getOnErrorEvents().size()); + + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + // we will have an exception + assertNotNull(command.getFailedExecutionException()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); + assertSaneHystrixRequestLog(1); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test behavior when some onNext are received and then a failure. + */ + @Test + public void testExecutionPartialSuccessWithFallback() { + try { + TestPartialSuccessWithFallback command = new TestPartialSuccessWithFallback(); + TestSubscriber ts = new TestSubscriber(); + command.toObservable().subscribe(ts); + ts.awaitTerminalEvent(); + ts.assertReceivedOnNext(Arrays.asList(false, true, false, true, false, true, false)); + ts.assertNoErrors(); + + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertNotNull(command.getFailedExecutionException()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.FAILURE, + HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); + assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); + assertSaneHystrixRequestLog(1); + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + @Test + public void testEarlyUnsubscribeDuringExecutionViaToObservable() { + class AsyncCommand extends HystrixObservableCommand { + + public AsyncCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); + } + + @Override + protected Observable construct() { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + try { + Thread.sleep(100); + return Observable.just(true); + } catch (InterruptedException ex) { + return Observable.error(ex); + } + } + }).subscribeOn(Schedulers.io()); + } + } + + HystrixObservableCommand cmd = new AsyncCommand(); + + final CountDownLatch latch = new CountDownLatch(1); + + Observable o = cmd.toObservable(); + Subscription s = o. + doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println("OnUnsubscribe"); + latch.countDown(); + } + }). + subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println("OnError : " + e); + } + + @Override + public void onNext(Boolean b) { + System.out.println("OnNext : " + b); + } + }); + + try { + s.unsubscribe(); + assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); + assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(cmd.isExecutionComplete()); + assertFalse(cmd.isExecutedInThread()); + System.out.println("EventCounts : " + cmd.getEventCounts()); + System.out.println("Execution Time : " + cmd.getExecutionTimeInMilliseconds()); + System.out.println("Is Successful : " + cmd.isSuccessfulExecution()); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + @Test + public void testEarlyUnsubscribeDuringExecutionViaObserve() { + class AsyncCommand extends HystrixObservableCommand { + + public AsyncCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); + } + + @Override + protected Observable construct() { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + try { + Thread.sleep(100); + return Observable.just(true); + } catch (InterruptedException ex) { + return Observable.error(ex); + } + } + }).subscribeOn(Schedulers.io()); + } + } + + HystrixObservableCommand cmd = new AsyncCommand(); + + final CountDownLatch latch = new CountDownLatch(1); + + Observable o = cmd.observe(); + Subscription s = o. + doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println("OnUnsubscribe"); + latch.countDown(); + } + }). + subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println("OnError : " + e); + } + + @Override + public void onNext(Boolean b) { + System.out.println("OnNext : " + b); + } + }); + + try { + s.unsubscribe(); + assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); + assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(cmd.isExecutionComplete()); + assertFalse(cmd.isExecutedInThread()); + System.out.println("EventCounts : " + cmd.getEventCounts()); + System.out.println("Execution Time : " + cmd.getExecutionTimeInMilliseconds()); + System.out.println("Is Successful : " + cmd.isSuccessfulExecution()); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + + @Test + public void testEarlyUnsubscribeDuringFallback() { + class AsyncCommand extends HystrixObservableCommand { + + public AsyncCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); + } + + @Override + protected Observable construct() { + return Observable.error(new RuntimeException("construct failure")); + } + + @Override + protected Observable resumeWithFallback() { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + try { + Thread.sleep(100); + return Observable.just(false); + } catch (InterruptedException ex) { + return Observable.error(ex); + } + } + }).subscribeOn(Schedulers.io()); + } + } + + HystrixObservableCommand cmd = new AsyncCommand(); + + final CountDownLatch latch = new CountDownLatch(1); + + Observable o = cmd.toObservable(); + Subscription s = o. + doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println("OnUnsubscribe"); + latch.countDown(); + } + }). + subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println("OnError : " + e); + } + + @Override + public void onNext(Boolean b) { + System.out.println("OnNext : " + b); + } + }); + + try { + Thread.sleep(10); //give fallback a chance to fire + s.unsubscribe(); + assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); + assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); + assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); + assertFalse(cmd.isExecutionComplete()); + assertFalse(cmd.isExecutedInThread()); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + + /* ******************************************************************************** */ + /* ******************************************************************************** */ + /* private HystrixCommand class implementations for unit testing */ + /* ******************************************************************************** */ + /* ******************************************************************************** */ + + static AtomicInteger uniqueNameCounter = new AtomicInteger(0); + + @Override + TestHystrixObservableCommand getCommand(ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, AbstractCommand.TryableSemaphore executionSemaphore, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("FlexibleObservable-" + uniqueNameCounter.getAndIncrement()); + return FlexibleTestHystrixObservableCommand.from(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + + @Override + TestHystrixObservableCommand getCommand(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, AbstractCommand.TryableSemaphore executionSemaphore, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + return FlexibleTestHystrixObservableCommand.from(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + + private static class FlexibleTestHystrixObservableCommand { + public static Integer EXECUTE_VALUE = 1; + public static Integer FALLBACK_VALUE = 11; + + public static AbstractFlexibleTestHystrixObservableCommand from(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, AbstractCommand.TryableSemaphore executionSemaphore, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + if (fallbackResult.equals(AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED)) { + return new FlexibleTestHystrixObservableCommandNoFallback(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } else { + return new FlexibleTestHystrixObservableCommandWithFallback(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + } + } + + private static class AbstractFlexibleTestHystrixObservableCommand extends TestHystrixObservableCommand { + private final AbstractTestHystrixCommand.ExecutionResult executionResult; + private final int executionLatency; + private final CacheEnabled cacheEnabled; + private final Object value; + + public AbstractFlexibleTestHystrixObservableCommand(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + super(testPropsBuilder(circuitBreaker) + .setCommandKey(commandKey) + .setCircuitBreaker(circuitBreaker) + .setMetrics(circuitBreaker.metrics) + .setThreadPool(threadPool) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(isolationStrategy) + .withExecutionTimeoutInMilliseconds(timeout) + .withCircuitBreakerEnabled(!circuitBreakerDisabled)) + .setExecutionSemaphore(executionSemaphore) + .setFallbackSemaphore(fallbackSemaphore)); + this.executionResult = executionResult; + this.executionLatency = executionLatency; + this.cacheEnabled = cacheEnabled; + this.value = value; + } + + @Override + protected Observable construct() { + if (executionResult == AbstractTestHystrixCommand.ExecutionResult.FAILURE) { + addLatency(executionLatency); + throw new RuntimeException("Execution Sync Failure for TestHystrixObservableCommand"); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE) { + addLatency(executionLatency); + throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, AbstractFlexibleTestHystrixObservableCommand.class, "Execution Hystrix Failure for TestHystrixObservableCommand", new RuntimeException("Execution Failure for TestHystrixObservableCommand"), new RuntimeException("Fallback Failure for TestHystrixObservableCommand")); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE) { + addLatency(executionLatency); + throw new NotWrappedByHystrixTestRuntimeException(); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.RECOVERABLE_ERROR) { + addLatency(executionLatency); + throw new java.lang.Error("Execution Sync Error for TestHystrixObservableCommand"); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.UNRECOVERABLE_ERROR) { + addLatency(executionLatency); + throw new OutOfMemoryError("Execution Sync OOME for TestHystrixObservableCommand"); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST) { + addLatency(executionLatency); + throw new HystrixBadRequestException("Execution Bad Request Exception for TestHystrixObservableCommand"); + } + return Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " construct() method has been subscribed to"); + addLatency(executionLatency); + if (executionResult == AbstractTestHystrixCommand.ExecutionResult.SUCCESS) { + subscriber.onNext(1); + subscriber.onCompleted(); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.MULTIPLE_EMITS_THEN_SUCCESS) { + subscriber.onNext(2); + subscriber.onNext(3); + subscriber.onNext(4); + subscriber.onNext(5); + subscriber.onCompleted(); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.MULTIPLE_EMITS_THEN_FAILURE) { + subscriber.onNext(6); + subscriber.onNext(7); + subscriber.onNext(8); + subscriber.onNext(9); + subscriber.onError(new RuntimeException("Execution Async Failure For TestHystrixObservableCommand after 4 emits")); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE) { + subscriber.onError(new RuntimeException("Execution Async Failure for TestHystrixObservableCommand after 0 emits")); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_HYSTRIX_FAILURE) { + subscriber.onError(new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, AbstractFlexibleTestHystrixObservableCommand.class, "Execution Hystrix Failure for TestHystrixObservableCommand", new RuntimeException("Execution Failure for TestHystrixObservableCommand"), new RuntimeException("Fallback Failure for TestHystrixObservableCommand"))); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_RECOVERABLE_ERROR) { + subscriber.onError(new java.lang.Error("Execution Async Error for TestHystrixObservableCommand")); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_UNRECOVERABLE_ERROR) { + subscriber.onError(new OutOfMemoryError("Execution Async OOME for TestHystrixObservableCommand")); + } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_BAD_REQUEST) { + subscriber.onError(new HystrixBadRequestException("Execution Async Bad Request Exception for TestHystrixObservableCommand")); + } else { + subscriber.onError(new RuntimeException("You passed in a executionResult enum that can't be represented in HystrixObservableCommand: " + executionResult)); + } + } + }); + } + + + @Override + public String getCacheKey() { + if (cacheEnabled == CacheEnabled.YES) + return value.toString(); + else + return null; + } + + protected void addLatency(int latency) { + if (latency > 0) { + try { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " About to sleep for : " + latency); + Thread.sleep(latency); + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Woke up from sleep!"); + } catch (InterruptedException e) { + e.printStackTrace(); + // ignore and sleep some more to simulate a dependency that doesn't obey interrupts + try { + Thread.sleep(latency); + } catch (Exception e2) { + // ignore + } + System.out.println("after interruption with extra sleep"); + } + } + } + } + + private static class FlexibleTestHystrixObservableCommandWithFallback extends AbstractFlexibleTestHystrixObservableCommand { + private final AbstractTestHystrixCommand.FallbackResult fallbackResult; + private final int fallbackLatency; + + public FlexibleTestHystrixObservableCommandWithFallback(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + super(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + this.fallbackResult = fallbackResult; + this.fallbackLatency = fallbackLatency; + } + + @Override + protected Observable resumeWithFallback() { + if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.FAILURE) { + addLatency(fallbackLatency); + throw new RuntimeException("Fallback Sync Failure for TestHystrixCommand"); + } else if (fallbackResult == FallbackResult.UNIMPLEMENTED) { + addLatency(fallbackLatency); + return super.resumeWithFallback(); + } + return Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + addLatency(fallbackLatency); + if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.SUCCESS) { + subscriber.onNext(11); + subscriber.onCompleted(); + } else if (fallbackResult == FallbackResult.MULTIPLE_EMITS_THEN_SUCCESS) { + subscriber.onNext(12); + subscriber.onNext(13); + subscriber.onNext(14); + subscriber.onNext(15); + subscriber.onCompleted(); + } else if (fallbackResult == FallbackResult.MULTIPLE_EMITS_THEN_FAILURE) { + subscriber.onNext(16); + subscriber.onNext(17); + subscriber.onNext(18); + subscriber.onNext(19); + subscriber.onError(new RuntimeException("Fallback Async Failure For TestHystrixObservableCommand after 4 emits")); + } else if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE) { + subscriber.onError(new RuntimeException("Fallback Async Failure for TestHystrixCommand after 0 fallback emits")); + } else { + subscriber.onError(new RuntimeException("You passed in a fallbackResult enum that can't be represented in HystrixObservableCommand: " + fallbackResult)); + } + } + }); + } + } + + private static class FlexibleTestHystrixObservableCommandNoFallback extends AbstractFlexibleTestHystrixObservableCommand { + public FlexibleTestHystrixObservableCommandNoFallback(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { + super(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); + } + } + + /** + * Successful execution - no fallback implementation. + */ + private static class SuccessfulTestCommand extends TestHystrixObservableCommand { + + public SuccessfulTestCommand(ExecutionIsolationStrategy isolationStrategy) { + this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)); + } + + public SuccessfulTestCommand(HystrixCommandProperties.Setter properties) { + super(testPropsBuilder().setCommandPropertiesDefaults(properties)); + } + + @Override + protected Observable construct() { + return Observable.just(true).subscribeOn(Schedulers.computation()); + } + + } + + /** + * Successful execution - no fallback implementation. + */ + private static class TestCommandWithMultipleValues extends TestHystrixObservableCommand { + + public TestCommandWithMultipleValues() { + this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)); + } + + public TestCommandWithMultipleValues(HystrixCommandProperties.Setter properties) { + super(testPropsBuilder().setCommandPropertiesDefaults(properties)); + } + + @Override + protected Observable construct() { + return Observable.just(true, false, true).subscribeOn(Schedulers.computation()); + } + + } + + private static class TestPartialSuccess extends TestHystrixObservableCommand { + + TestPartialSuccess() { + super(TestHystrixObservableCommand.testPropsBuilder()); + } + + @Override + protected Observable construct() { + return Observable.just(1, 2, 3) + .concatWith(Observable. error(new RuntimeException("forced error"))) + .subscribeOn(Schedulers.computation()); + } + + } + + private static class TestPartialSuccessWithFallback extends TestHystrixObservableCommand { + + TestPartialSuccessWithFallback() { + super(TestHystrixObservableCommand.testPropsBuilder()); + } + + public TestPartialSuccessWithFallback(ExecutionIsolationStrategy isolationStrategy) { + this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)); + } + + public TestPartialSuccessWithFallback(HystrixCommandProperties.Setter properties) { + super(testPropsBuilder().setCommandPropertiesDefaults(properties)); + } + + @Override + protected Observable construct() { + return Observable.just(false, true, false) + .concatWith(Observable.error(new RuntimeException("forced error"))) + .subscribeOn(Schedulers.computation()); + } + + @Override + protected Observable resumeWithFallback() { + return Observable.just(true, false, true, false); + } + + } + + + + /** + * Successful execution - no fallback implementation. + */ + private static class DynamicOwnerTestCommand extends TestHystrixObservableCommand { + + public DynamicOwnerTestCommand(HystrixCommandGroupKey owner) { + super(testPropsBuilder().setOwner(owner)); + } + + @Override + protected Observable construct() { + System.out.println("successfully executed"); + return Observable.just(true).subscribeOn(Schedulers.computation()); + } + + } + + /** + * Successful execution - no fallback implementation. + */ + private static class DynamicOwnerAndKeyTestCommand extends TestHystrixObservableCommand { + + public DynamicOwnerAndKeyTestCommand(HystrixCommandGroupKey owner, HystrixCommandKey key) { + super(testPropsBuilder().setOwner(owner).setCommandKey(key).setCircuitBreaker(null).setMetrics(null)); + // we specifically are NOT passing in a circuit breaker here so we test that it creates a new one correctly based on the dynamic key + } + + @Override + protected Observable construct() { + System.out.println("successfully executed"); + return Observable.just(true).subscribeOn(Schedulers.computation()); + } + + } + + /** + * Failed execution with unknown exception (not HystrixException) - no fallback implementation. + */ + private static class UnknownFailureTestCommandWithoutFallback extends TestHystrixObservableCommand { + + private final boolean asyncException; + + private UnknownFailureTestCommandWithoutFallback(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + super(testPropsBuilder(isolationStrategy, new TestCircuitBreaker())); + this.asyncException = asyncException; + } + + @Override + protected Observable construct() { + System.out.println("*** simulated failed execution ***"); + RuntimeException ex = new RuntimeException("we failed with an unknown issue"); + if (asyncException) { + return Observable.error(ex); + } else { + throw ex; + } + } + } + + /** + * Failed execution with known exception (HystrixException) - no fallback implementation. + */ + private static class KnownFailureTestCommandWithoutFallback extends TestHystrixObservableCommand { + + final boolean asyncException; + + private KnownFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + super(testPropsBuilder(isolationStrategy, circuitBreaker).setMetrics(circuitBreaker.metrics)); + this.asyncException = asyncException; + } + + @Override + protected Observable construct() { + System.out.println("*** simulated failed execution ***"); + RuntimeException ex = new RuntimeException("we failed with a simulated issue"); + if (asyncException) { + return Observable.error(ex); + } else { + throw ex; + } + } + } + + /** + * Failed execution - fallback implementation successfully returns value. + */ + private static class KnownFailureTestCommandWithFallback extends TestHystrixObservableCommand { + + private final boolean asyncException; + + public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + super(testPropsBuilder(isolationStrategy, circuitBreaker).setMetrics(circuitBreaker.metrics)); + this.asyncException = asyncException; + } + + public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, boolean fallbackEnabled, boolean asyncException) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withFallbackEnabled(fallbackEnabled).withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); + this.asyncException = asyncException; + } + + @Override + protected Observable construct() { + System.out.println("*** simulated failed execution ***"); + RuntimeException ex = new RuntimeException("we failed with a simulated issue"); + if (asyncException) { + return Observable.error(ex); + } else { + throw ex; + } + } + + @Override + protected Observable resumeWithFallback() { + return Observable.just(false).subscribeOn(Schedulers.computation()); + } + } + + /** + * Failed execution with {@link HystrixBadRequestException} + */ + private static class KnownHystrixBadRequestFailureTestCommand extends TestHystrixObservableCommand { + + public final static boolean ASYNC_EXCEPTION = true; + public final static boolean SYNC_EXCEPTION = false; + + private final boolean asyncException; + + public KnownHystrixBadRequestFailureTestCommand(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { + super(testPropsBuilder(isolationStrategy, circuitBreaker).setMetrics(circuitBreaker.metrics)); + this.asyncException = asyncException; + } + + @Override + protected Observable construct() { + System.out.println("*** simulated failed with HystrixBadRequestException ***"); + RuntimeException ex = new HystrixBadRequestException("we failed with a simulated issue"); + if (asyncException) { + return Observable.error(ex); + } else { + throw ex; + } + } + } + + /** + * Failed execution - fallback implementation throws exception. + */ + private static class KnownFailureTestCommandWithFallbackFailure extends TestHystrixObservableCommand { + + private final boolean asyncConstructException; + private final boolean asyncFallbackException; + + private KnownFailureTestCommandWithFallbackFailure(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy, boolean asyncConstructException, boolean asyncFallbackException) { + super(testPropsBuilder(isolationStrategy, circuitBreaker).setMetrics(circuitBreaker.metrics)); + this.asyncConstructException = asyncConstructException; + this.asyncFallbackException = asyncFallbackException; + } + + @Override + protected Observable construct() { + RuntimeException ex = new RuntimeException("we failed with a simulated issue"); + System.out.println("*** simulated failed execution ***"); + if (asyncConstructException) { + return Observable.error(ex); + } else { + throw ex; + } + } + + @Override + protected Observable resumeWithFallback() { + RuntimeException ex = new RuntimeException("failed while getting fallback"); + if (asyncFallbackException) { + return Observable.error(ex); + } else { + throw ex; + } + } + } + + /** + * A Command implementation that supports caching. + */ + private static class SuccessfulCacheableCommand extends TestHystrixObservableCommand { + + private final boolean cacheEnabled; + private volatile boolean executed = false; + private final T value; + + public SuccessfulCacheableCommand(TestCircuitBreaker circuitBreaker, boolean cacheEnabled, T value) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD))); + this.value = value; + this.cacheEnabled = cacheEnabled; + } + + @Override + protected Observable construct() { + executed = true; + System.out.println("successfully executed"); + return Observable.just(value).subscribeOn(Schedulers.computation()); + } + + public boolean isCommandRunningInThread() { + return super.getProperties().executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD); + } + + @Override + public String getCacheKey() { + if (cacheEnabled) + return value.toString(); + else + return null; + } + } + + /** + * A Command implementation that supports caching. + */ + private static class SuccessfulCacheableCommandViaSemaphore extends TestHystrixObservableCommand { + + private final boolean cacheEnabled; + private volatile boolean executed = false; + private final String value; + + public SuccessfulCacheableCommandViaSemaphore(TestCircuitBreaker circuitBreaker, boolean cacheEnabled, String value) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); + this.value = value; + this.cacheEnabled = cacheEnabled; + } + + @Override + protected Observable construct() { + executed = true; + System.out.println("successfully executed"); + return Observable.just(value).subscribeOn(Schedulers.computation()); + } + + public boolean isCommandRunningInThread() { + return super.getProperties().executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD); + } + + @Override + public String getCacheKey() { + if (cacheEnabled) + return value; + else + return null; + } + } + + /** + * A Command implementation that supports caching and execution takes a while. + *

+ * Used to test scenario where Futures are returned with a backing call still executing. + */ + private static class SlowCacheableCommand extends TestHystrixObservableCommand { + + private final String value; + private final int duration; + private volatile boolean executed = false; + + public SlowCacheableCommand(TestCircuitBreaker circuitBreaker, String value, int duration) { + super(testPropsBuilder() + .setCommandKey(HystrixCommandKey.Factory.asKey("ObservableSlowCacheable")) + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + this.value = value; + this.duration = duration; + } + + @Override + protected Observable construct() { + executed = true; + return Observable.just(value).delay(duration, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()) + .doOnNext(new Action1() { + + @Override + public void call(String t1) { + System.out.println("successfully executed"); + } + + }); + } + + @Override + public String getCacheKey() { + return value; + } + } + + /** + * Successful execution - no fallback implementation, circuit-breaker disabled. + */ + private static class TestCommandWithoutCircuitBreaker extends TestHystrixObservableCommand { + + private TestCommandWithoutCircuitBreaker() { + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withCircuitBreakerEnabled(false))); + } + + @Override + protected Observable construct() { + System.out.println("successfully executed"); + return Observable.just(true).subscribeOn(Schedulers.computation()); + } + + } + + private static class NoRequestCacheTimeoutWithoutFallback extends TestHystrixObservableCommand { + public NoRequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionTimeoutInMilliseconds(200).withCircuitBreakerEnabled(false))); + + // we want it to timeout + } + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + System.out.println(">>>> Sleep Interrupted: " + e.getMessage()); + // e.printStackTrace(); + } + s.onNext(true); + s.onCompleted(); + } + + }).subscribeOn(Schedulers.computation()); + } + + @Override + public String getCacheKey() { + return null; + } + } + + /** + * The run() will take time. Configurable fallback implementation. + */ + private static class TestSemaphoreCommand extends TestHystrixObservableCommand { + + private final long executionSleep; + + private final static int RESULT_SUCCESS = 1; + private final static int RESULT_FAILURE = 2; + private final static int RESULT_BAD_REQUEST_EXCEPTION = 3; + + private final int resultBehavior; + + private final static int FALLBACK_SUCCESS = 10; + private final static int FALLBACK_NOT_IMPLEMENTED = 11; + private final static int FALLBACK_FAILURE = 12; + + private final int fallbackBehavior; + + private final static boolean FALLBACK_FAILURE_SYNC = false; + private final static boolean FALLBACK_FAILURE_ASYNC = true; + + private final boolean asyncFallbackException; + + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep, int resultBehavior, int fallbackBehavior) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) + .withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); + this.executionSleep = executionSleep; + this.resultBehavior = resultBehavior; + this.fallbackBehavior = fallbackBehavior; + this.asyncFallbackException = FALLBACK_FAILURE_ASYNC; + } + + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, long executionSleep, int resultBehavior, int fallbackBehavior) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)) + .setExecutionSemaphore(semaphore)); + this.executionSleep = executionSleep; + this.resultBehavior = resultBehavior; + this.fallbackBehavior = fallbackBehavior; + this.asyncFallbackException = FALLBACK_FAILURE_ASYNC; + } + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + Thread.sleep(executionSleep); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (resultBehavior == RESULT_SUCCESS) { + subscriber.onNext(true); + subscriber.onCompleted(); + } else if (resultBehavior == RESULT_FAILURE) { + subscriber.onError(new RuntimeException("TestSemaphoreCommand failure")); + } else if (resultBehavior == RESULT_BAD_REQUEST_EXCEPTION) { + subscriber.onError(new HystrixBadRequestException("TestSemaphoreCommand BadRequestException")); + } else { + subscriber.onError(new IllegalStateException("Didn't use a proper enum for result behavior")); + } + } + }); + } + + @Override + protected Observable resumeWithFallback() { + if (fallbackBehavior == FALLBACK_SUCCESS) { + return Observable.just(false); + } else if (fallbackBehavior == FALLBACK_FAILURE) { + RuntimeException ex = new RuntimeException("fallback failure"); + if (asyncFallbackException) { + return Observable.error(ex); + } else { + throw ex; + } + } else { //FALLBACK_NOT_IMPLEMENTED + return super.resumeWithFallback(); + } + } + } + + /** + * The construct() will take time once subscribed to. No fallback implementation. + * + * Used for making sure Thread and Semaphore isolation are separated from each other. + */ + private static class TestThreadIsolationWithSemaphoreSetSmallCommand extends TestHystrixObservableCommand { + + private final Action0 action; + + private TestThreadIsolationWithSemaphoreSetSmallCommand(TestCircuitBreaker circuitBreaker, int poolSize, Action0 action) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(TestThreadIsolationWithSemaphoreSetSmallCommand.class.getSimpleName())) + .setThreadPoolPropertiesDefaults(HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder() + .withCoreSize(poolSize).withMaximumSize(poolSize).withMaxQueueSize(0)) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) + .withExecutionIsolationSemaphoreMaxConcurrentRequests(1))); + this.action = action; + } + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + action.call(); + s.onNext(true); + s.onCompleted(); + } + + }); + } + } + + /** + * Semaphore based command that allows caller to use latches to know when it has started and signal when it + * would like the command to finish + */ + private static class LatchedSemaphoreCommand extends TestHystrixObservableCommand { + + private final CountDownLatch startLatch, waitLatch; + + /** + * + * @param circuitBreaker circuit breaker (passed in so it may be shared) + * @param semaphore semaphore (passed in so it may be shared) + * @param startLatch + * this command calls {@link CountDownLatch#countDown()} immediately upon running + * @param waitLatch + * this command calls {@link CountDownLatch#await()} once it starts + * to run. The caller can use the latch to signal the command to finish + */ + private LatchedSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphoreActual semaphore, CountDownLatch startLatch, CountDownLatch waitLatch) { + this("Latched", circuitBreaker, semaphore, startLatch, waitLatch); + } + + private LatchedSemaphoreCommand(String commandName, TestCircuitBreaker circuitBreaker, TryableSemaphoreActual semaphore, CountDownLatch startLatch, CountDownLatch waitLatch) { + super(testPropsBuilder() + .setCommandKey(HystrixCommandKey.Factory.asKey(commandName)) + .setCircuitBreaker(circuitBreaker) + .setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionTimeoutEnabled(false) + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) + .withCircuitBreakerEnabled(false)) + .setExecutionSemaphore(semaphore)); + this.startLatch = startLatch; + this.waitLatch = waitLatch; + } + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + //signals caller that run has started + startLatch.countDown(); + try { + // waits for caller to countDown latch + waitLatch.await(); + s.onNext(true); + s.onCompleted(); + } catch (InterruptedException e) { + e.printStackTrace(); + s.onNext(false); + s.onCompleted(); + } + } + }).subscribeOn(Schedulers.newThread()); + } + + @Override + protected Observable resumeWithFallback() { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + startLatch.countDown(); + return Observable.just(false); + } + }); + } + } + + /** + * The construct() will take time once subscribed to. Contains fallback. + */ + private static class TestSemaphoreCommandWithFallback extends TestHystrixObservableCommand { + + private final long executionSleep; + private final Observable fallback; + + private TestSemaphoreCommandWithFallback(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep, Boolean fallback) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); + this.executionSleep = executionSleep; + this.fallback = Observable.just(fallback); + } + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + try { + Thread.sleep(executionSleep); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + s.onNext(true); + s.onCompleted(); + } + + }).subscribeOn(Schedulers.io()); + } + + @Override + protected Observable resumeWithFallback() { + return fallback; + } + + } + + private static class InterruptibleCommand extends TestHystrixObservableCommand { + + public InterruptibleCommand(TestCircuitBreaker circuitBreaker, boolean shouldInterrupt) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationThreadInterruptOnTimeout(shouldInterrupt) + .withExecutionTimeoutInMilliseconds(100))); + } + + private volatile boolean hasBeenInterrupted; + + public boolean hasBeenInterrupted() + { + return hasBeenInterrupted; + } + + @Override + protected Observable construct() { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + try { + Thread.sleep(1000); + } + catch (InterruptedException e) { + System.out.println("Interrupted!"); + e.printStackTrace(); + hasBeenInterrupted = true; + } + + return Observable.just(hasBeenInterrupted); + } + }).subscribeOn(Schedulers.io()); + + } + } + + private static class RequestCacheNullPointerExceptionCase extends TestHystrixObservableCommand { + public RequestCacheNullPointerExceptionCase(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionTimeoutInMilliseconds(200))); + // we want it to timeout + } + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + s.onNext(true); + s.onCompleted(); + } + + }).subscribeOn(Schedulers.computation()); + } + + @Override + protected Observable resumeWithFallback() { + return Observable.just(false).subscribeOn(Schedulers.computation()); + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class RequestCacheTimeoutWithoutFallback extends TestHystrixObservableCommand { + public RequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionTimeoutInMilliseconds(200))); + // we want it to timeout + } + + @Override + protected Observable construct() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + System.out.println(">>>> Sleep Interrupted: " + e.getMessage()); + // e.printStackTrace(); + } + + s.onNext(true); + s.onCompleted(); + } + + }).subscribeOn(Schedulers.computation()); + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class RequestCacheThreadRejectionWithoutFallback extends TestHystrixObservableCommand { + + final CountDownLatch completionLatch; + + public RequestCacheThreadRejectionWithoutFallback(TestCircuitBreaker circuitBreaker, CountDownLatch completionLatch) { + super(testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)) + .setCircuitBreaker(circuitBreaker) + .setMetrics(circuitBreaker.metrics) + .setThreadPool(new HystrixThreadPool() { + + @Override + public ThreadPoolExecutor getExecutor() { + return null; + } + + @Override + public void markThreadExecution() { + + } + + @Override + public void markThreadCompletion() { + + } + + @Override + public void markThreadRejection() { + + } + + @Override + public boolean isQueueSpaceAvailable() { + // always return false so we reject everything + return false; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + @Override + public Scheduler getScheduler(Func0 shouldInterruptThread) { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread); + } + + })); + this.completionLatch = completionLatch; + } + + @Override + protected Observable construct() { + try { + if (completionLatch.await(1000, TimeUnit.MILLISECONDS)) { + throw new RuntimeException("timed out waiting on completionLatch"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return Observable.just(true); + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class CommandWithErrorThrown extends TestHystrixObservableCommand { + + private final boolean asyncException; + + public CommandWithErrorThrown(TestCircuitBreaker circuitBreaker, boolean asyncException) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + this.asyncException = asyncException; + } + + @Override + protected Observable construct() { + Error error = new Error("simulated java.lang.Error message"); + if (asyncException) { + return Observable.error(error); + } else { + throw error; + } + } + } + + private static class CommandWithCheckedException extends TestHystrixObservableCommand { + + public CommandWithCheckedException(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Observable construct() { + return Observable.error(new IOException("simulated checked exception message")); + } + + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestCacheTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestCacheTest.java new file mode 100644 index 0000000..5774aa0 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestCacheTest.java @@ -0,0 +1,117 @@ +/** + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; + +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import rx.Subscription; +import rx.subjects.ReplaySubject; + +public class HystrixRequestCacheTest { + + @Test + public void testCache() { + HystrixConcurrencyStrategy strategy = HystrixConcurrencyStrategyDefault.getInstance(); + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + HystrixRequestCache cache1 = HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey("command1"), strategy); + cache1.putIfAbsent("valueA", new TestObservable("a1")); + cache1.putIfAbsent("valueA", new TestObservable("a2")); + cache1.putIfAbsent("valueB", new TestObservable("b1")); + + HystrixRequestCache cache2 = HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey("command2"), strategy); + cache2.putIfAbsent("valueA", new TestObservable("a3")); + + assertEquals("a1", cache1.get("valueA").toObservable().toBlocking().last()); + assertEquals("b1", cache1.get("valueB").toObservable().toBlocking().last()); + + assertEquals("a3", cache2.get("valueA").toObservable().toBlocking().last()); + assertNull(cache2.get("valueB")); + } catch (Exception e) { + fail("Exception: " + e.getMessage()); + e.printStackTrace(); + } finally { + context.shutdown(); + } + + context = HystrixRequestContext.initializeContext(); + try { + // with a new context the instance should have nothing in it + HystrixRequestCache cache = HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey("command1"), strategy); + assertNull(cache.get("valueA")); + assertNull(cache.get("valueB")); + } finally { + context.shutdown(); + } + } + + @Test(expected = IllegalStateException.class) + public void testCacheWithoutContext() { + HystrixRequestCache.getInstance( + HystrixCommandKey.Factory.asKey("command1"), + HystrixConcurrencyStrategyDefault.getInstance() + ).get("any"); + } + + @Test + public void testClearCache() { + HystrixConcurrencyStrategy strategy = HystrixConcurrencyStrategyDefault.getInstance(); + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + HystrixRequestCache cache1 = HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey("command1"), strategy); + cache1.putIfAbsent("valueA", new TestObservable("a1")); + assertEquals("a1", cache1.get("valueA").toObservable().toBlocking().last()); + cache1.clear("valueA"); + assertNull(cache1.get("valueA")); + } catch (Exception e) { + fail("Exception: " + e.getMessage()); + e.printStackTrace(); + } finally { + context.shutdown(); + } + } + + @Test + public void testCacheWithoutRequestContext() { + HystrixConcurrencyStrategy strategy = HystrixConcurrencyStrategyDefault.getInstance(); + //HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + HystrixRequestCache cache1 = HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey("command1"), strategy); + //this should fail, as there's no HystrixRequestContext instance to place the cache into + cache1.putIfAbsent("valueA", new TestObservable("a1")); + fail("should throw an exception on cache put"); + } catch (Exception e) { + //expected + e.printStackTrace(); + } + } + + private static class TestObservable extends HystrixCachedObservable { + public TestObservable(String arg) { + super(Observable.just(arg)); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestLogTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestLogTest.java new file mode 100644 index 0000000..b8bfff5 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestLogTest.java @@ -0,0 +1,242 @@ +/** + * 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 com.hystrix.junit.HystrixRequestContextRule; +import static org.junit.Assert.assertEquals; + +import org.junit.Rule; +import org.junit.Test; + +import rx.Observable; + +public class HystrixRequestLogTest { + + private static final String DIGITS_REGEX = "\\[\\d+"; + + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + @Test + public void testSuccess() { + new TestCommand("A", false, true).execute(); + String log = HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString(); + // strip the actual count so we can compare reliably + log = log.replaceAll(DIGITS_REGEX, "["); + assertEquals("TestCommand[SUCCESS][ms]", log); + } + + @Test + public void testSuccessFromCache() { + // 1 success + new TestCommand("A", false, true).execute(); + // 4 success from cache + new TestCommand("A", false, true).execute(); + new TestCommand("A", false, true).execute(); + new TestCommand("A", false, true).execute(); + new TestCommand("A", false, true).execute(); + String log = HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString(); + // strip the actual count so we can compare reliably + log = log.replaceAll(DIGITS_REGEX, "["); + assertEquals("TestCommand[SUCCESS][ms], TestCommand[SUCCESS, RESPONSE_FROM_CACHE][ms]x4", log); + } + + @Test + public void testFailWithFallbackSuccess() { + // 1 failure + new TestCommand("A", true, false).execute(); + // 4 failures from cache + new TestCommand("A", true, false).execute(); + new TestCommand("A", true, false).execute(); + new TestCommand("A", true, false).execute(); + new TestCommand("A", true, false).execute(); + String log = HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString(); + // strip the actual count so we can compare reliably + log = log.replaceAll(DIGITS_REGEX, "["); + assertEquals("TestCommand[FAILURE, FALLBACK_SUCCESS][ms], TestCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][ms]x4", log); + } + + @Test + public void testFailWithFallbackFailure() { + // 1 failure + try { + new TestCommand("A", true, true).execute(); + } catch (Exception e) { + } + // 1 failure from cache + try { + new TestCommand("A", true, true).execute(); + } catch (Exception e) { + } + String log = HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString(); + // strip the actual count so we can compare reliably + log = log.replaceAll(DIGITS_REGEX, "["); + assertEquals("TestCommand[FAILURE, FALLBACK_FAILURE][ms], TestCommand[FAILURE, FALLBACK_FAILURE, RESPONSE_FROM_CACHE][ms]", log); + } + + @Test + public void testTimeout() { + Observable result = null; + + // 1 timeout + try { + for (int i = 0; i < 1; i++) { + result = new TestCommand("A", false, false, true).observe(); + } + } catch (Exception e) { + } + try { + result.toBlocking().single(); + } catch (Throwable ex) { + //ex.printStackTrace(); + } + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " -> done with awaiting all observables"); + String log = HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString(); + // strip the actual count so we can compare reliably + log = log.replaceAll(DIGITS_REGEX, "["); + assertEquals("TestCommand[TIMEOUT, FALLBACK_MISSING][ms]", log); + } + + @Test + public void testManyTimeouts() { + for (int i = 0; i < 10; i++) { + testTimeout(); + ctx.reset(); + } + } + + @Test + public void testMultipleCommands() { + // 1 success + new TestCommand("GetData", "A", false, false).execute(); + + // 1 success + new TestCommand("PutData", "B", false, false).execute(); + + // 1 success + new TestCommand("GetValues", "C", false, false).execute(); + + // 1 success from cache + new TestCommand("GetValues", "C", false, false).execute(); + + // 1 failure + try { + new TestCommand("A", true, true).execute(); + } catch (Exception e) { + } + // 1 failure from cache + try { + new TestCommand("A", true, true).execute(); + } catch (Exception e) { + } + String log = HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString(); + // strip the actual count so we can compare reliably + log = log.replaceAll(DIGITS_REGEX, "["); + assertEquals("GetData[SUCCESS][ms], PutData[SUCCESS][ms], GetValues[SUCCESS][ms], GetValues[SUCCESS, RESPONSE_FROM_CACHE][ms], TestCommand[FAILURE, FALLBACK_FAILURE][ms], TestCommand[FAILURE, FALLBACK_FAILURE, RESPONSE_FROM_CACHE][ms]", log); + } + + @Test + public void testMaxLimit() { + for (int i = 0; i < HystrixRequestLog.MAX_STORAGE; i++) { + new TestCommand("A", false, true).execute(); + } + // then execute again some more + for (int i = 0; i < 10; i++) { + new TestCommand("A", false, true).execute(); + } + + assertEquals(HystrixRequestLog.MAX_STORAGE, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + private static class TestCommand extends HystrixCommand { + + private final String value; + private final boolean fail; + private final boolean failOnFallback; + private final boolean timeout; + private final boolean useFallback; + private final boolean useCache; + + public TestCommand(String commandName, String value, boolean fail, boolean failOnFallback) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RequestLogTestCommand")).andCommandKey(HystrixCommandKey.Factory.asKey(commandName))); + this.value = value; + this.fail = fail; + this.failOnFallback = failOnFallback; + this.timeout = false; + this.useFallback = true; + this.useCache = true; + } + + public TestCommand(String value, boolean fail, boolean failOnFallback) { + super(HystrixCommandGroupKey.Factory.asKey("RequestLogTestCommand")); + this.value = value; + this.fail = fail; + this.failOnFallback = failOnFallback; + this.timeout = false; + this.useFallback = true; + this.useCache = true; + } + + public TestCommand(String value, boolean fail, boolean failOnFallback, boolean timeout) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RequestLogTestCommand")).andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500))); + this.value = value; + this.fail = fail; + this.failOnFallback = failOnFallback; + this.timeout = timeout; + this.useFallback = false; + this.useCache = false; + } + + @Override + protected String run() { + System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis()); + if (fail) { + throw new RuntimeException("forced failure"); + } else if (timeout) { + try { + Thread.sleep(10000); + System.out.println("Woke up from sleep!"); + } catch (InterruptedException ex) { + System.out.println(Thread.currentThread().getName() + " Interrupted by timeout"); + } + } + return value; + } + + @Override + protected String getFallback() { + if (useFallback) { + if (failOnFallback) { + throw new RuntimeException("forced fallback failure"); + } else { + return value + "-fallback"; + } + } else { + throw new UnsupportedOperationException("no fallback implemented"); + } + } + + @Override + protected String getCacheKey() { + if (useCache) { + return value; + } else { + return null; + } + } + + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixSubclassCommandTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixSubclassCommandTest.java new file mode 100644 index 0000000..d0ab97e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixSubclassCommandTest.java @@ -0,0 +1,162 @@ +/** + * 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 com.hystrix.junit.HystrixRequestContextRule; +import org.junit.Rule; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class HystrixSubclassCommandTest { + + private final static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("GROUP"); + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + @Test + public void testFallback() { + HystrixCommand superCmd = new SuperCommand("cache", false); + assertEquals(2, superCmd.execute().intValue()); + + HystrixCommand subNoOverridesCmd = new SubCommandNoOverride("cache", false); + assertEquals(2, subNoOverridesCmd.execute().intValue()); + + HystrixCommand subOverriddenFallbackCmd = new SubCommandOverrideFallback("cache", false); + assertEquals(3, subOverriddenFallbackCmd.execute().intValue()); + } + + @Test + public void testRequestCacheSuperClass() { + HystrixCommand superCmd1 = new SuperCommand("cache", true); + assertEquals(1, superCmd1.execute().intValue()); + HystrixCommand superCmd2 = new SuperCommand("cache", true); + assertEquals(1, superCmd2.execute().intValue()); + HystrixCommand superCmd3 = new SuperCommand("no-cache", true); + assertEquals(1, superCmd3.execute().intValue()); + System.out.println("REQ LOG : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + HystrixRequestLog reqLog = HystrixRequestLog.getCurrentRequest(); + assertEquals(3, reqLog.getAllExecutedCommands().size()); + List> infos = new ArrayList>(reqLog.getAllExecutedCommands()); + HystrixInvokableInfo info1 = infos.get(0); + assertEquals("SuperCommand", info1.getCommandKey().name()); + assertEquals(1, info1.getExecutionEvents().size()); + HystrixInvokableInfo info2 = infos.get(1); + assertEquals("SuperCommand", info2.getCommandKey().name()); + assertEquals(2, info2.getExecutionEvents().size()); + assertEquals(HystrixEventType.RESPONSE_FROM_CACHE, info2.getExecutionEvents().get(1)); + HystrixInvokableInfo info3 = infos.get(2); + assertEquals("SuperCommand", info3.getCommandKey().name()); + assertEquals(1, info3.getExecutionEvents().size()); + } + + @Test + public void testRequestCacheSubclassNoOverrides() { + HystrixCommand subCmd1 = new SubCommandNoOverride("cache", true); + assertEquals(1, subCmd1.execute().intValue()); + HystrixCommand subCmd2 = new SubCommandNoOverride("cache", true); + assertEquals(1, subCmd2.execute().intValue()); + HystrixCommand subCmd3 = new SubCommandNoOverride("no-cache", true); + assertEquals(1, subCmd3.execute().intValue()); + System.out.println("REQ LOG : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + HystrixRequestLog reqLog = HystrixRequestLog.getCurrentRequest(); + assertEquals(3, reqLog.getAllExecutedCommands().size()); + List> infos = new ArrayList>(reqLog.getAllExecutedCommands()); + HystrixInvokableInfo info1 = infos.get(0); + assertEquals("SubCommandNoOverride", info1.getCommandKey().name()); + assertEquals(1, info1.getExecutionEvents().size()); + HystrixInvokableInfo info2 = infos.get(1); + assertEquals("SubCommandNoOverride", info2.getCommandKey().name()); + assertEquals(2, info2.getExecutionEvents().size()); + assertEquals(HystrixEventType.RESPONSE_FROM_CACHE, info2.getExecutionEvents().get(1)); + HystrixInvokableInfo info3 = infos.get(2); + assertEquals("SubCommandNoOverride", info3.getCommandKey().name()); + assertEquals(1, info3.getExecutionEvents().size()); + } + + @Test + public void testRequestLogSuperClass() { + HystrixCommand superCmd = new SuperCommand("cache", true); + assertEquals(1, superCmd.execute().intValue()); + System.out.println("REQ LOG : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + HystrixRequestLog reqLog = HystrixRequestLog.getCurrentRequest(); + assertEquals(1, reqLog.getAllExecutedCommands().size()); + HystrixInvokableInfo info = reqLog.getAllExecutedCommands().iterator().next(); + assertEquals("SuperCommand", info.getCommandKey().name()); + } + + @Test + public void testRequestLogSubClassNoOverrides() { + HystrixCommand subCmd = new SubCommandNoOverride("cache", true); + assertEquals(1, subCmd.execute().intValue()); + System.out.println("REQ LOG : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + HystrixRequestLog reqLog = HystrixRequestLog.getCurrentRequest(); + assertEquals(1, reqLog.getAllExecutedCommands().size()); + HystrixInvokableInfo info = reqLog.getAllExecutedCommands().iterator().next(); + assertEquals("SubCommandNoOverride", info.getCommandKey().name()); + } + + public static class SuperCommand extends HystrixCommand { + private final String uniqueArg; + private final boolean shouldSucceed; + + SuperCommand(String uniqueArg, boolean shouldSucceed) { + super(Setter.withGroupKey(groupKey)); + this.uniqueArg = uniqueArg; + this.shouldSucceed = shouldSucceed; + } + + @Override + protected Integer run() throws Exception { + if (shouldSucceed) { + return 1; + } else { + throw new RuntimeException("unit test failure"); + } + } + + @Override + protected Integer getFallback() { + return 2; + } + + @Override + protected String getCacheKey() { + return uniqueArg; + } + } + + public static class SubCommandNoOverride extends SuperCommand { + SubCommandNoOverride(String uniqueArg, boolean shouldSucceed) { + super(uniqueArg, shouldSucceed); + } + } + + public static class SubCommandOverrideFallback extends SuperCommand { + SubCommandOverrideFallback(String uniqueArg, boolean shouldSucceed) { + super(uniqueArg, shouldSucceed); + } + + @Override + protected Integer getFallback() { + return 3; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixTest.java new file mode 100644 index 0000000..1a80124 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixTest.java @@ -0,0 +1,509 @@ +/** + * 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 command = new HystrixCommand(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 command = new HystrixCommand(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() { + @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 command = new HystrixCommand(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 command2 = new HystrixCommand(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 command = new HystrixCommand(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 command = new HystrixCommand(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 command = new HystrixCommand(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 command = new HystrixCommand(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 command2 = new HystrixCommand(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 observableCmd = new SynchronousObservableCommand(); + + assertNull(Hystrix.getCurrentThreadExecutingCommand()); + + final CountDownLatch latch = new CountDownLatch(1); + + observableCmd.observe().subscribe(new Subscriber() { + @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 observableCmd = new AsynchronousObservableCommand(); +// +// assertNull(Hystrix.getCurrentThreadExecutingCommand()); +// +// final CountDownLatch latch = new CountDownLatch(1); +// +// observableCmd.observe().subscribe(new Subscriber() { +// @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> commands = new ArrayList>(); + for (int i = 0; i < NUM_COMMANDS; i++) { + commands.add(Observable.defer(new Func0>() { + @Override + public Observable call() { + return new AsynchronousObservableCommand().observe(); + } + })); + } + + final AtomicBoolean exceptionFound = new AtomicBoolean(false); + + final CountDownLatch latch = new CountDownLatch(1); + + Observable.merge(commands).subscribe(new Subscriber() { + @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 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 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 { + + protected SynchronousObservableCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")) + .andCommandKey(HystrixCommandKey.Factory.asKey("SyncObservable")) + .andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(1000)) + ); + } + + @Override + protected Observable construct() { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber 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 { + + protected AsynchronousObservableCommand() { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")) + .andCommandKey(HystrixCommandKey.Factory.asKey("AsyncObservable")) + .andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(1000)) + ); + } + + @Override + protected Observable construct() { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber 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 { + 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; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolMetricsTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolMetricsTest.java new file mode 100644 index 0000000..ed29a25 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolMetricsTest.java @@ -0,0 +1,74 @@ +/** + * 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 com.netflix.hystrix.metric.consumer.RollingThreadPoolEventCounterStream; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +public class HystrixThreadPoolMetricsTest { + + private static final HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("HystrixThreadPoolMetrics-UnitTest"); + private static final HystrixThreadPoolKey tpKey = HystrixThreadPoolKey.Factory.asKey("HystrixThreadPoolMetrics-ThreadPool"); + + @Before + public void resetAll() { + HystrixThreadPoolMetrics.reset(); + } + + @Test + public void shouldYieldNoExecutedTasksOnStartup() throws Exception { + //given + final Collection instances = HystrixThreadPoolMetrics.getInstances(); + + //then + assertEquals(0, instances.size()); + + } + @Test + public void shouldReturnOneExecutedTask() throws Exception { + //given + RollingThreadPoolEventCounterStream.getInstance(tpKey, 10, 100).startCachingStreamValuesIfUnstarted(); + + new NoOpHystrixCommand().execute(); + Thread.sleep(100); + + final Collection instances = HystrixThreadPoolMetrics.getInstances(); + + //then + assertEquals(1, instances.size()); + HystrixThreadPoolMetrics metrics = instances.iterator().next(); + assertEquals(1, instances.iterator().next().getRollingCountThreadsExecuted()); + } + + private static class NoOpHystrixCommand extends HystrixCommand { + public NoOpHystrixCommand() { + super(Setter.withGroupKey(groupKey) + .andThreadPoolKey(tpKey) + .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withMetricsRollingStatisticalWindowInMilliseconds(100))); + } + + @Override + protected Void run() throws Exception { + System.out.println("Run in thread : " + Thread.currentThread().getName()); + return null; + } + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolPropertiesTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolPropertiesTest.java new file mode 100644 index 0000000..c1a7da2 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolPropertiesTest.java @@ -0,0 +1,287 @@ +/** + * Copyright 2016 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 static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Test; + +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.strategy.properties.HystrixProperty; + +public class HystrixThreadPoolPropertiesTest { + + /** + * Base properties for unit testing. + */ + /* package */static HystrixThreadPoolProperties.Setter getUnitTestPropertiesBuilder() { + return HystrixThreadPoolProperties.Setter() + .withCoreSize(10)// core size of thread pool + .withMaximumSize(15) //maximum size of thread pool + .withKeepAliveTimeMinutes(1)// minutes to keep a thread alive (though in practice this doesn't get used as by default we set a fixed size) + .withMaxQueueSize(100)// size of queue (but we never allow it to grow this big ... this can't be dynamically changed so we use 'queueSizeRejectionThreshold' to artificially limit and reject) + .withQueueSizeRejectionThreshold(10)// number of items in queue at which point we reject (this can be dyamically changed) + .withMetricsRollingStatisticalWindowInMilliseconds(10000)// milliseconds for rolling number + .withMetricsRollingStatisticalWindowBuckets(10);// number of buckets in rolling number (10 1-second buckets) + } + + /** + * Return a static representation of the properties with values from the Builder so that UnitTests can create properties that are not affected by the actual implementations which pick up their + * values dynamically. + * + * @param builder builder for a {@link HystrixThreadPoolProperties} + * @return HystrixThreadPoolProperties + */ + /* package */static HystrixThreadPoolProperties asMock(final HystrixThreadPoolProperties.Setter builder) { + return new HystrixThreadPoolProperties(TestThreadPoolKey.TEST) { + + @Override + public HystrixProperty coreSize() { + return HystrixProperty.Factory.asProperty(builder.getCoreSize()); + } + + @Override + public HystrixProperty maximumSize() { + return HystrixProperty.Factory.asProperty(builder.getMaximumSize()); + } + + @Override + public HystrixProperty keepAliveTimeMinutes() { + return HystrixProperty.Factory.asProperty(builder.getKeepAliveTimeMinutes()); + } + + @Override + public HystrixProperty maxQueueSize() { + return HystrixProperty.Factory.asProperty(builder.getMaxQueueSize()); + } + + @Override + public HystrixProperty queueSizeRejectionThreshold() { + return HystrixProperty.Factory.asProperty(builder.getQueueSizeRejectionThreshold()); + } + + @Override + public HystrixProperty metricsRollingStatisticalWindowInMilliseconds() { + return HystrixProperty.Factory.asProperty(builder.getMetricsRollingStatisticalWindowInMilliseconds()); + } + + @Override + public HystrixProperty metricsRollingStatisticalWindowBuckets() { + return HystrixProperty.Factory.asProperty(builder.getMetricsRollingStatisticalWindowBuckets()); + } + + }; + + } + + private static enum TestThreadPoolKey implements HystrixThreadPoolKey { + TEST + } + + @After + public void cleanup() { + ConfigurationManager.getConfigInstance().clear(); + } + + @Test + public void testSetNeitherCoreNorMaximumSizeWithDivergenceDisallowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withAllowMaximumSizeToDivergeFromCoreSize(false)) { + }; + + assertEquals(HystrixThreadPoolProperties.default_coreSize, properties.coreSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_maximumSize, properties.maximumSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_coreSize, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetNeitherCoreNorMaximumSizeWithDivergenceAllowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withAllowMaximumSizeToDivergeFromCoreSize(true)) { + }; + + assertEquals(HystrixThreadPoolProperties.default_coreSize, properties.coreSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_maximumSize, properties.maximumSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_maximumSize, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetCoreSizeOnlyWithDivergenceDisallowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withCoreSize(14) + .withAllowMaximumSizeToDivergeFromCoreSize(false)) { + }; + + assertEquals(14, properties.coreSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_maximumSize, properties.maximumSize().get().intValue()); + assertEquals(14, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetCoreSizeOnlyWithDivergenceAllowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withCoreSize(14) + .withAllowMaximumSizeToDivergeFromCoreSize(true)) { + }; + + assertEquals(14, properties.coreSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_maximumSize, properties.maximumSize().get().intValue()); + assertEquals(14, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetMaximumSizeOnlyLowerThanDefaultCoreSizeWithDivergenceDisallowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withMaximumSize(3) + .withAllowMaximumSizeToDivergeFromCoreSize(false)) { + }; + + assertEquals(HystrixThreadPoolProperties.default_coreSize, properties.coreSize().get().intValue()); + assertEquals(3, properties.maximumSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_coreSize, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetMaximumSizeOnlyLowerThanDefaultCoreSizeWithDivergenceAllowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withMaximumSize(3) + .withAllowMaximumSizeToDivergeFromCoreSize(true)) { + }; + + assertEquals(HystrixThreadPoolProperties.default_coreSize, properties.coreSize().get().intValue()); + assertEquals(3, properties.maximumSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_coreSize, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetMaximumSizeOnlyGreaterThanDefaultCoreSizeWithDivergenceDisallowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withMaximumSize(21) + .withAllowMaximumSizeToDivergeFromCoreSize(false)) { + }; + + assertEquals(HystrixThreadPoolProperties.default_coreSize, properties.coreSize().get().intValue()); + assertEquals(21, properties.maximumSize().get().intValue()); + assertEquals(HystrixThreadPoolProperties.default_coreSize, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetMaximumSizeOnlyGreaterThanDefaultCoreSizeWithDivergenceAllowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withMaximumSize(21) + .withAllowMaximumSizeToDivergeFromCoreSize(true)) { + }; + + assertEquals(HystrixThreadPoolProperties.default_coreSize, properties.coreSize().get().intValue()); + assertEquals(21, properties.maximumSize().get().intValue()); + assertEquals(21, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetCoreSizeLessThanMaximumSizeWithDivergenceDisallowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withCoreSize(2) + .withMaximumSize(8) + .withAllowMaximumSizeToDivergeFromCoreSize(false)) { + }; + + assertEquals(2, properties.coreSize().get().intValue()); + assertEquals(8, properties.maximumSize().get().intValue()); + assertEquals(2, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetCoreSizeLessThanMaximumSizeWithDivergenceAllowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withCoreSize(2) + .withMaximumSize(8) + .withAllowMaximumSizeToDivergeFromCoreSize(true)) { + }; + + assertEquals(2, properties.coreSize().get().intValue()); + assertEquals(8, properties.maximumSize().get().intValue()); + assertEquals(8, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetCoreSizeEqualToMaximumSizeDivergenceDisallowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withCoreSize(7) + .withMaximumSize(7) + .withAllowMaximumSizeToDivergeFromCoreSize(false)) { + }; + + assertEquals(7, properties.coreSize().get().intValue()); + assertEquals(7, properties.maximumSize().get().intValue()); + assertEquals(7, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetCoreSizeEqualToMaximumSizeDivergenceAllowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withCoreSize(7) + .withMaximumSize(7) + .withAllowMaximumSizeToDivergeFromCoreSize(true)) { + }; + + assertEquals(7, properties.coreSize().get().intValue()); + assertEquals(7, properties.maximumSize().get().intValue()); + assertEquals(7, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetCoreSizeGreaterThanMaximumSizeWithDivergenceDisallowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withCoreSize(12) + .withMaximumSize(8) + .withAllowMaximumSizeToDivergeFromCoreSize(false)) { + }; + + assertEquals(12, properties.coreSize().get().intValue()); + assertEquals(8, properties.maximumSize().get().intValue()); + assertEquals(12, (int) properties.actualMaximumSize()); + } + + @Test + public void testSetCoreSizeGreaterThanMaximumSizeWithDivergenceAllowed() { + HystrixThreadPoolProperties properties = new HystrixThreadPoolProperties(TestThreadPoolKey.TEST, + HystrixThreadPoolProperties.Setter() + .withCoreSize(12) + .withMaximumSize(8) + .withAllowMaximumSizeToDivergeFromCoreSize(true)) { + }; + + assertEquals(12, properties.coreSize().get().intValue()); + assertEquals(8, properties.maximumSize().get().intValue()); + assertEquals(12, (int) properties.actualMaximumSize()); + } +} + + diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolTest.java new file mode 100644 index 0000000..faf22f9 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolTest.java @@ -0,0 +1,171 @@ +/** + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.hamcrest.core.Is.is; + +import com.netflix.hystrix.HystrixThreadPool.Factory; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.*; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherFactory; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherThreadPool; + +import org.junit.Before; +import org.junit.Test; + +import rx.Scheduler; +import rx.functions.Action0; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class HystrixThreadPoolTest { + @Before + public void setup() { + Hystrix.reset(); + } + + @Test + public void testShutdown() { + // other unit tests will probably have run before this so get the count + int count = Factory.threadPools.size(); + + HystrixThreadPool pool = Factory.getInstance(HystrixThreadPoolKey.Factory.asKey("threadPoolFactoryTest"), + HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder()); + + assertEquals(count + 1, Factory.threadPools.size()); + assertFalse(pool.getExecutor().isShutdown()); + + Factory.shutdown(); + + // ensure all pools were removed from the cache + assertEquals(0, Factory.threadPools.size()); + assertTrue(pool.getExecutor().isShutdown()); + } + + @Test + public void testShutdownWithWait() { + // other unit tests will probably have run before this so get the count + int count = Factory.threadPools.size(); + + HystrixThreadPool pool = Factory.getInstance(HystrixThreadPoolKey.Factory.asKey("threadPoolFactoryTest"), + HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder()); + + assertEquals(count + 1, Factory.threadPools.size()); + assertFalse(pool.getExecutor().isShutdown()); + + Factory.shutdown(1, TimeUnit.SECONDS); + + // ensure all pools were removed from the cache + assertEquals(0, Factory.threadPools.size()); + assertTrue(pool.getExecutor().isShutdown()); + } + + private static class HystrixMetricsPublisherThreadPoolContainer implements HystrixMetricsPublisherThreadPool { + private final HystrixThreadPoolMetrics hystrixThreadPoolMetrics; + + private HystrixMetricsPublisherThreadPoolContainer(HystrixThreadPoolMetrics hystrixThreadPoolMetrics) { + this.hystrixThreadPoolMetrics = hystrixThreadPoolMetrics; + } + + @Override + public void initialize() { + } + + public HystrixThreadPoolMetrics getHystrixThreadPoolMetrics() { + return hystrixThreadPoolMetrics; + } + } + + @Test + public void ensureThreadPoolInstanceIsTheOneRegisteredWithMetricsPublisherAndThreadPoolCache() throws IllegalAccessException, NoSuchFieldException { + HystrixPlugins.getInstance().registerMetricsPublisher(new HystrixMetricsPublisher() { + @Override + public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + return new HystrixMetricsPublisherThreadPoolContainer(metrics); + } + }); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("threadPoolFactoryConcurrencyTest"); + HystrixThreadPool poolOne = new HystrixThreadPool.HystrixThreadPoolDefault( + threadPoolKey, HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder()); + HystrixThreadPool poolTwo = new HystrixThreadPool.HystrixThreadPoolDefault( + threadPoolKey, HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder()); + + assertThat(poolOne.getExecutor(), is(poolTwo.getExecutor())); //Now that we get the threadPool from the metrics object, this will always be equal + HystrixMetricsPublisherThreadPoolContainer hystrixMetricsPublisherThreadPool = + (HystrixMetricsPublisherThreadPoolContainer)HystrixMetricsPublisherFactory + .createOrRetrievePublisherForThreadPool(threadPoolKey, null, null); + ThreadPoolExecutor threadPoolExecutor = hystrixMetricsPublisherThreadPool.getHystrixThreadPoolMetrics().getThreadPool(); + + //assert that both HystrixThreadPools share the same ThreadPoolExecutor as the one in HystrixMetricsPublisherThreadPool + assertTrue(threadPoolExecutor.equals(poolOne.getExecutor()) && threadPoolExecutor.equals(poolTwo.getExecutor())); + assertFalse(threadPoolExecutor.isShutdown()); + + //Now the HystrixThreadPool ALWAYS has the same reference to the ThreadPoolExecutor so that it no longer matters which + //wins to be inserted into the HystrixThreadPool.Factory.threadPools cache. + } + + @Test(timeout = 2500) + public void testUnsubscribeHystrixThreadPool() throws InterruptedException { + // methods are package-private so can't test it somewhere else + HystrixThreadPool pool = Factory.getInstance(HystrixThreadPoolKey.Factory.asKey("threadPoolFactoryTest"), + HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder()); + + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch start = new CountDownLatch(1); + final CountDownLatch end = new CountDownLatch(1); + + HystrixContextScheduler hcs = new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), pool); + + Scheduler.Worker w = hcs.createWorker(); + + try { + w.schedule(new Action0() { + @Override + public void call() { + start.countDown(); + try { + try { + Thread.sleep(5000); + } catch (InterruptedException ex) { + interrupted.set(true); + } + } finally { + end.countDown(); + } + } + }); + + start.await(); + + w.unsubscribe(); + + end.await(); + + Factory.shutdown(); + + assertTrue(interrupted.get()); + } finally { + w.unsubscribe(); + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/InspectableBuilder.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/InspectableBuilder.java new file mode 100644 index 0000000..3d5ff9d --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/InspectableBuilder.java @@ -0,0 +1,109 @@ +/** + * 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; + +public interface InspectableBuilder { + public TestCommandBuilder getBuilder(); + + public enum CommandKeyForUnitTest implements HystrixCommandKey { + KEY_ONE, KEY_TWO + } + + public enum CommandGroupForUnitTest implements HystrixCommandGroupKey { + OWNER_ONE, OWNER_TWO + } + + public enum ThreadPoolKeyForUnitTest implements HystrixThreadPoolKey { + THREAD_POOL_ONE, THREAD_POOL_TWO + } + + public static class TestCommandBuilder { + HystrixCommandGroupKey owner = CommandGroupForUnitTest.OWNER_ONE; + HystrixCommandKey dependencyKey = null; + HystrixThreadPoolKey threadPoolKey = null; + HystrixCircuitBreaker circuitBreaker; + HystrixThreadPool threadPool = null; + HystrixCommandProperties.Setter commandPropertiesDefaults = HystrixCommandPropertiesTest.getUnitTestPropertiesSetter(); + HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults = HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder(); + HystrixCommandMetrics metrics; + AbstractCommand.TryableSemaphore fallbackSemaphore = null; + AbstractCommand.TryableSemaphore executionSemaphore = null; + TestableExecutionHook executionHook = new TestableExecutionHook(); + + TestCommandBuilder(HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy) { + this.commandPropertiesDefaults = HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy); + } + + TestCommandBuilder setOwner(HystrixCommandGroupKey owner) { + this.owner = owner; + return this; + } + + TestCommandBuilder setCommandKey(HystrixCommandKey dependencyKey) { + this.dependencyKey = dependencyKey; + return this; + } + + TestCommandBuilder setThreadPoolKey(HystrixThreadPoolKey threadPoolKey) { + this.threadPoolKey = threadPoolKey; + return this; + } + + TestCommandBuilder setCircuitBreaker(HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker) { + this.circuitBreaker = circuitBreaker; + if (circuitBreaker != null) { + this.metrics = circuitBreaker.metrics; + } + return this; + } + + TestCommandBuilder setThreadPool(HystrixThreadPool threadPool) { + this.threadPool = threadPool; + return this; + } + + TestCommandBuilder setCommandPropertiesDefaults(HystrixCommandProperties.Setter commandPropertiesDefaults) { + this.commandPropertiesDefaults = commandPropertiesDefaults; + return this; + } + + TestCommandBuilder setThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) { + this.threadPoolPropertiesDefaults = threadPoolPropertiesDefaults; + return this; + } + + TestCommandBuilder setMetrics(HystrixCommandMetrics metrics) { + this.metrics = metrics; + return this; + } + + TestCommandBuilder setFallbackSemaphore(AbstractCommand.TryableSemaphore fallbackSemaphore) { + this.fallbackSemaphore = fallbackSemaphore; + return this; + } + + TestCommandBuilder setExecutionSemaphore(AbstractCommand.TryableSemaphore executionSemaphore) { + this.executionSemaphore = executionSemaphore; + return this; + } + + TestCommandBuilder setExecutionHook(TestableExecutionHook executionHook) { + this.executionHook = executionHook; + return this; + } + + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/NotWrappedByHystrixTestException.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/NotWrappedByHystrixTestException.java new file mode 100644 index 0000000..69f8ed8 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/NotWrappedByHystrixTestException.java @@ -0,0 +1,8 @@ +package com.netflix.hystrix; + +import com.netflix.hystrix.exception.ExceptionNotWrappedByHystrix; + +public class NotWrappedByHystrixTestException extends Exception implements ExceptionNotWrappedByHystrix { + private static final long serialVersionUID = 1L; + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/NotWrappedByHystrixTestRuntimeException.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/NotWrappedByHystrixTestRuntimeException.java new file mode 100644 index 0000000..e0dde9c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/NotWrappedByHystrixTestRuntimeException.java @@ -0,0 +1,11 @@ +package com.netflix.hystrix; + +import com.netflix.hystrix.exception.ExceptionNotWrappedByHystrix; + +public class NotWrappedByHystrixTestRuntimeException extends RuntimeException implements ExceptionNotWrappedByHystrix { + private static final long serialVersionUID = 1L; + + public NotWrappedByHystrixTestRuntimeException() { + super("Raw exception for TestHystrixCommand"); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestHystrixCommand.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestHystrixCommand.java new file mode 100644 index 0000000..f27aa45 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestHystrixCommand.java @@ -0,0 +1,53 @@ +/** + * 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 com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; + +abstract public class TestHystrixCommand extends HystrixCommand implements AbstractTestHystrixCommand { + + private final TestCommandBuilder builder; + + public TestHystrixCommand(TestCommandBuilder builder) { + super(builder.owner, builder.dependencyKey, builder.threadPoolKey, builder.circuitBreaker, builder.threadPool, + builder.commandPropertiesDefaults, builder.threadPoolPropertiesDefaults, builder.metrics, + builder.fallbackSemaphore, builder.executionSemaphore, TEST_PROPERTIES_FACTORY, builder.executionHook); + this.builder = builder; + } + + public TestHystrixCommand(TestCommandBuilder builder, HystrixCommandExecutionHook executionHook) { + super(builder.owner, builder.dependencyKey, builder.threadPoolKey, builder.circuitBreaker, builder.threadPool, + builder.commandPropertiesDefaults, builder.threadPoolPropertiesDefaults, builder.metrics, + builder.fallbackSemaphore, builder.executionSemaphore, TEST_PROPERTIES_FACTORY, executionHook); + this.builder = builder; + } + + public TestCommandBuilder getBuilder() { + return builder; + } + + static TestCommandBuilder testPropsBuilder() { + return new TestCommandBuilder(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); + } + + static TestCommandBuilder testPropsBuilder(HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker) { + return new TestCommandBuilder(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD).setCircuitBreaker(circuitBreaker); + } + + static TestCommandBuilder testPropsBuilder(HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy) { + return new TestCommandBuilder(isolationStrategy); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestHystrixObservableCommand.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestHystrixObservableCommand.java new file mode 100644 index 0000000..6c80f85 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestHystrixObservableCommand.java @@ -0,0 +1,44 @@ +/** + * 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; + +abstract public class TestHystrixObservableCommand extends HystrixObservableCommand implements AbstractTestHystrixCommand { + + private final TestCommandBuilder builder; + + public TestHystrixObservableCommand(TestCommandBuilder builder) { + super(builder.owner, builder.dependencyKey, builder.threadPoolKey, builder.circuitBreaker, builder.threadPool, + builder.commandPropertiesDefaults, builder.threadPoolPropertiesDefaults, builder.metrics, + builder.fallbackSemaphore, builder.executionSemaphore, TEST_PROPERTIES_FACTORY, builder.executionHook); + this.builder = builder; + } + + public TestCommandBuilder getBuilder() { + return builder; + } + + static TestCommandBuilder testPropsBuilder() { + return new TestCommandBuilder(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + } + + static TestCommandBuilder testPropsBuilder(HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker) { + return new TestCommandBuilder(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE).setCircuitBreaker(circuitBreaker); + } + + static TestCommandBuilder testPropsBuilder(HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy, HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker) { + return new TestCommandBuilder(isolationStrategy).setCircuitBreaker(circuitBreaker); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestableExecutionHook.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestableExecutionHook.java new file mode 100644 index 0000000..d45b439 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/TestableExecutionHook.java @@ -0,0 +1,266 @@ +/** + * 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 com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import rx.Notification; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +class TestableExecutionHook extends HystrixCommandExecutionHook { + + private static void recordHookCall(StringBuilder sequenceRecorder, String methodName) { + sequenceRecorder.append(methodName).append(" - "); + } + + StringBuilder executionSequence = new StringBuilder(); + List> commandEmissions = new ArrayList>(); + List> executionEmissions = new ArrayList>(); + List> fallbackEmissions = new ArrayList>(); + + public boolean commandEmissionsMatch(int numOnNext, int numOnError, int numOnCompleted) { + return eventsMatch(commandEmissions, numOnNext, numOnError, numOnCompleted); + } + + public boolean executionEventsMatch(int numOnNext, int numOnError, int numOnCompleted) { + return eventsMatch(executionEmissions, numOnNext, numOnError, numOnCompleted); + } + + public boolean fallbackEventsMatch(int numOnNext, int numOnError, int numOnCompleted) { + return eventsMatch(fallbackEmissions, numOnNext, numOnError, numOnCompleted); + } + + private boolean eventsMatch(List> l, int numOnNext, int numOnError, int numOnCompleted) { + boolean matchFailed = false; + int actualOnNext = 0; + int actualOnError = 0; + int actualOnCompleted = 0; + + + if (l.size() != numOnNext + numOnError + numOnCompleted) { + System.out.println("Actual : " + l + ", Expected : " + numOnNext + " OnNexts, " + numOnError + " OnErrors, " + numOnCompleted + " OnCompleted"); + return false; + } + for (int n = 0; n < numOnNext; n++) { + Notification current = l.get(n); + if (!current.isOnNext()) { + matchFailed = true; + } else { + actualOnNext++; + } + } + for (int e = numOnNext; e < numOnNext + numOnError; e++) { + Notification current = l.get(e); + if (!current.isOnError()) { + matchFailed = true; + } else { + actualOnError++; + } + } + for (int c = numOnNext + numOnError; c < numOnNext + numOnError + numOnCompleted; c++) { + Notification current = l.get(c); + if (!current.isOnCompleted()) { + matchFailed = true; + } else { + actualOnCompleted++; + } + } + if (matchFailed) { + System.out.println("Expected : " + numOnNext + " OnNexts, " + numOnError + " OnErrors, and " + numOnCompleted); + System.out.println("Actual : " + actualOnNext + " OnNexts, " + actualOnError + " OnErrors, and " + actualOnCompleted); + } + return !matchFailed; + } + + public Throwable getCommandException() { + return getException(commandEmissions); + } + + public Throwable getExecutionException() { + return getException(executionEmissions); + } + + public Throwable getFallbackException() { + return getException(fallbackEmissions); + } + + private Throwable getException(List> l) { + for (Notification n: l) { + if (n.isOnError()) { + n.getThrowable().printStackTrace(); + return n.getThrowable(); + } + } + return null; + } + + @Override + public void onStart(HystrixInvokable commandInstance) { + super.onStart(commandInstance); + recordHookCall(executionSequence, "onStart"); + } + + @Override + public T onEmit(HystrixInvokable commandInstance, T value) { + commandEmissions.add(Notification.createOnNext(value)); + recordHookCall(executionSequence, "onEmit"); + return super.onEmit(commandInstance, value); + } + + @Override + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { + commandEmissions.add(Notification.createOnError(e)); + recordHookCall(executionSequence, "onError"); + return super.onError(commandInstance, failureType, e); + } + + @Override + public void onSuccess(HystrixInvokable commandInstance) { + commandEmissions.add(Notification.createOnCompleted()); + recordHookCall(executionSequence, "onSuccess"); + super.onSuccess(commandInstance); + } + + @Override + public void onThreadStart(HystrixInvokable commandInstance) { + super.onThreadStart(commandInstance); + recordHookCall(executionSequence, "onThreadStart"); + } + + @Override + public void onThreadComplete(HystrixInvokable commandInstance) { + super.onThreadComplete(commandInstance); + recordHookCall(executionSequence, "onThreadComplete"); + } + + @Override + public void onExecutionStart(HystrixInvokable commandInstance) { + recordHookCall(executionSequence, "onExecutionStart"); + super.onExecutionStart(commandInstance); + } + + @Override + public T onExecutionEmit(HystrixInvokable commandInstance, T value) { + executionEmissions.add(Notification.createOnNext(value)); + recordHookCall(executionSequence, "onExecutionEmit"); + return super.onExecutionEmit(commandInstance, value); + } + + @Override + public Exception onExecutionError(HystrixInvokable commandInstance, Exception e) { + executionEmissions.add(Notification.createOnError(e)); + recordHookCall(executionSequence, "onExecutionError"); + return super.onExecutionError(commandInstance, e); + } + + @Override + public void onExecutionSuccess(HystrixInvokable commandInstance) { + executionEmissions.add(Notification.createOnCompleted()); + recordHookCall(executionSequence, "onExecutionSuccess"); + super.onExecutionSuccess(commandInstance); + } + + @Override + public void onFallbackStart(HystrixInvokable commandInstance) { + super.onFallbackStart(commandInstance); + recordHookCall(executionSequence, "onFallbackStart"); + } + + @Override + public T onFallbackEmit(HystrixInvokable commandInstance, T value) { + fallbackEmissions.add(Notification.createOnNext(value)); + recordHookCall(executionSequence, "onFallbackEmit"); + return super.onFallbackEmit(commandInstance, value); + } + + @Override + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { + fallbackEmissions.add(Notification.createOnError(e)); + recordHookCall(executionSequence, "onFallbackError"); + return super.onFallbackError(commandInstance, e); + } + + @Override + public void onFallbackSuccess(HystrixInvokable commandInstance) { + fallbackEmissions.add(Notification.createOnCompleted()); + recordHookCall(executionSequence, "onFallbackSuccess"); + super.onFallbackSuccess(commandInstance); + } + + @Override + public void onCacheHit(HystrixInvokable commandInstance) { + super.onCacheHit(commandInstance); + recordHookCall(executionSequence, "onCacheHit"); + } + + @Override + public void onUnsubscribe(HystrixInvokable commandInstance) { + super.onUnsubscribe(commandInstance); + recordHookCall(executionSequence, "onUnsubscribe"); + } + + /** + * DEPRECATED METHODS FOLLOW. The string representation starts with `!` to distinguish + */ + + AtomicInteger startExecute = new AtomicInteger(); + Object endExecuteSuccessResponse = null; + Exception endExecuteFailureException = null; + HystrixRuntimeException.FailureType endExecuteFailureType = null; + AtomicInteger startRun = new AtomicInteger(); + Object runSuccessResponse = null; + Exception runFailureException = null; + AtomicInteger startFallback = new AtomicInteger(); + Object fallbackSuccessResponse = null; + Exception fallbackFailureException = null; + AtomicInteger threadStart = new AtomicInteger(); + AtomicInteger threadComplete = new AtomicInteger(); + AtomicInteger cacheHit = new AtomicInteger(); + + @Override + public T onFallbackSuccess(HystrixInvokable commandInstance, T response) { + recordHookCall(executionSequence, "!onFallbackSuccess"); + return super.onFallbackSuccess(commandInstance, response); + } + + @Override + public T onComplete(HystrixInvokable commandInstance, T response) { + recordHookCall(executionSequence, "!onComplete"); + return super.onComplete(commandInstance, response); + } + + @Override + public void onRunStart(HystrixInvokable commandInstance) { + super.onRunStart(commandInstance); + recordHookCall(executionSequence, "!onRunStart"); + } + + @Override + public T onRunSuccess(HystrixInvokable commandInstance, T response) { + recordHookCall(executionSequence, "!onRunSuccess"); + return super.onRunSuccess(commandInstance, response); + } + + @Override + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { + recordHookCall(executionSequence, "!onRunError"); + return super.onRunError(commandInstance, e); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/UnsubscribedTasksRequestCacheTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/UnsubscribedTasksRequestCacheTest.java new file mode 100644 index 0000000..bce4de0 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/UnsubscribedTasksRequestCacheTest.java @@ -0,0 +1,130 @@ +/** + * Copyright 2017 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 com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + +public class UnsubscribedTasksRequestCacheTest { + + private AtomicBoolean encounteredCommandException = new AtomicBoolean(false); + private AtomicInteger numOfExecutions = new AtomicInteger(0); + + public class CommandExecutionHook extends HystrixCommandExecutionHook { + + @Override + public Exception onError(HystrixInvokable commandInstance, HystrixRuntimeException.FailureType failureType, Exception e) { + e.printStackTrace(); + encounteredCommandException.set(true); + return e; + } + } + + public class CommandUsingRequestCache extends HystrixCommand { + + private final int value; + + protected CommandUsingRequestCache(int value) { + super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); + this.value = value; + } + + @Override + protected Boolean run() { + numOfExecutions.getAndIncrement(); + System.out.println(Thread.currentThread().getName() + " run()"); + return value == 0 || value % 2 == 0; + } + + @Override + protected String getCacheKey() { + return String.valueOf(value); + } + } + + @Before + public void init() { + HystrixPlugins.reset(); + } + + @After + public void reset() { + HystrixPlugins.reset(); + } + + @Test + public void testOneCommandIsUnsubscribed() throws ExecutionException, InterruptedException { + + HystrixPlugins.getInstance().registerCommandExecutionHook(new CommandExecutionHook()); + final HystrixRequestContext context = HystrixRequestContext.initializeContext(); + final AtomicInteger numCacheResponses = new AtomicInteger(0); + + try { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + Future futureCommand2a = executorService.submit(createCommandRunnable(context, numCacheResponses)); + Future futureCommand2b = executorService.submit(createCommandRunnable(context, numCacheResponses)); + + futureCommand2a.get(); + futureCommand2b.get(); + + assertEquals(1, numCacheResponses.get()); + assertEquals(1, numOfExecutions.get()); + assertFalse(encounteredCommandException.get()); + } finally { + context.shutdown(); + } + } + + private Runnable createCommandRunnable(final HystrixRequestContext context, final AtomicInteger numCacheResponses) { + return new Runnable() { + + public void run() { + + HystrixRequestContext.setContextOnCurrentThread(context); + + CommandUsingRequestCache command2a = new CommandUsingRequestCache(2); + Future resultCommand2a = command2a.queue(); + + try { + assertTrue(resultCommand2a.get()); + System.out.println(Thread.currentThread() + " " + command2a.isResponseFromCache()); + if (command2a.isResponseFromCache()) { + numCacheResponses.getAndIncrement(); + } + } catch (Exception e) { + fail("Exception: " + e.getMessage()); + } + } + }; + } + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/collapser/CollapsedRequestSubjectTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/collapser/CollapsedRequestSubjectTest.java new file mode 100644 index 0000000..1e8423e --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/collapser/CollapsedRequestSubjectTest.java @@ -0,0 +1,182 @@ +/** + * 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.collapser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.junit.Test; + +import rx.Observable; + +public class CollapsedRequestSubjectTest { + @Test + public void testSetResponseSuccess() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future v = o.toBlocking().toFuture(); + + cr.setResponse("theResponse"); + + // fetch value + assertEquals("theResponse", v.get()); + } + + @Test + public void testSetNullResponseSuccess() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future v = o.toBlocking().toFuture(); + + cr.setResponse(null); + + // fetch value + assertEquals(null, v.get()); + } + + @Test + public void testSetException() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future v = o.toBlocking().toFuture(); + + cr.setException(new RuntimeException("anException")); + + // fetch value + try { + v.get(); + fail("expected exception"); + } catch (ExecutionException e) { + assertEquals("anException", e.getCause().getMessage()); + } + } + + @Test + public void testSetExceptionAfterResponse() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future v = o.toBlocking().toFuture(); + + cr.setResponse("theResponse"); + + try { + cr.setException(new RuntimeException("anException")); + fail("expected IllegalState"); + } catch (IllegalStateException e) { + + } + + assertEquals("theResponse", v.get()); + } + + @Test + public void testSetResponseAfterException() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future v = o.toBlocking().toFuture(); + + cr.setException(new RuntimeException("anException")); + + try { + cr.setResponse("theResponse"); + fail("expected IllegalState"); + } catch (IllegalStateException e) { + + } + + try { + v.get(); + fail("expected exception"); + } catch (ExecutionException e) { + assertEquals("anException", e.getCause().getMessage()); + } + } + + @Test + public void testSetResponseDuplicate() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future v = o.toBlocking().toFuture(); + + cr.setResponse("theResponse"); + + try { + cr.setResponse("theResponse2"); + fail("expected IllegalState"); + } catch (IllegalStateException e) { + + } + + assertEquals("theResponse", v.get()); + } + + @Test(expected = CancellationException.class) + public void testSetResponseAfterUnsubscribe() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future f = o.toBlocking().toFuture(); + + // cancel/unsubscribe + f.cancel(true); + + try { + cr.setResponse("theResponse"); + } catch (IllegalStateException e) { + fail("this should have done nothing as it was unsubscribed already"); + } + + // expect CancellationException after cancelling + f.get(); + } + + @Test(expected = CancellationException.class) + public void testSetExceptionAfterUnsubscribe() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future f = o.toBlocking().toFuture(); + + // cancel/unsubscribe + f.cancel(true); + + try { + cr.setException(new RuntimeException("anException")); + } catch (IllegalStateException e) { + fail("this should have done nothing as it was unsubscribed already"); + } + + // expect CancellationException after cancelling + f.get(); + } + + @Test + public void testUnsubscribeAfterSetResponse() throws InterruptedException, ExecutionException { + CollapsedRequestSubject cr = new CollapsedRequestSubject("hello"); + Observable o = cr.toObservable(); + Future v = o.toBlocking().toFuture(); + + cr.setResponse("theResponse"); + + // unsubscribe after the value is sent + v.cancel(true); + + // still get value as it was set before canceling + assertEquals("theResponse", v.get()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/config/HystrixConfigurationStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/config/HystrixConfigurationStreamTest.java new file mode 100644 index 0000000..c3ea1d8 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/config/HystrixConfigurationStreamTest.java @@ -0,0 +1,326 @@ +/** + * Copyright 2016 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.config; + +import com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.config.HystrixConfiguration; +import com.netflix.hystrix.config.HystrixConfigurationStream; +import com.netflix.hystrix.metric.CommandStreamTest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.schedulers.Schedulers; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class HystrixConfigurationStreamTest extends CommandStreamTest { + + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + HystrixConfigurationStream stream; + private final static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Config"); + private final static HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("Command"); + + @Before + public void init() { + stream = HystrixConfigurationStream.getNonSingletonInstanceOnlyUsedInUnitTests(10); + } + + @Test + public void testStreamHasData() throws Exception { + final AtomicBoolean commandShowsUp = new AtomicBoolean(false); + final AtomicBoolean threadPoolShowsUp = new AtomicBoolean(false); + final CountDownLatch latch = new CountDownLatch(1); + final int NUM = 10; + + for (int i = 0; i < 2; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.observe(); + } + + stream.observe().take(NUM).subscribe( + new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnError : " + e); + latch.countDown(); + } + + @Override + public void onNext(HystrixConfiguration configuration) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Received data with : " + configuration.getCommandConfig().size() + " commands"); + if (configuration.getCommandConfig().containsKey(commandKey)) { + commandShowsUp.set(true); + } + if (!configuration.getThreadPoolConfig().isEmpty()) { + threadPoolShowsUp.set(true); + } + } + }); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(commandShowsUp.get()); + assertTrue(threadPoolShowsUp.get()); + } + + @Test + public void testTwoSubscribersOneUnsubscribes() throws Exception { + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final AtomicInteger payloads1 = new AtomicInteger(0); + final AtomicInteger payloads2 = new AtomicInteger(0); + + Subscription s1 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(HystrixConfiguration configuration) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnNext : " + configuration); + payloads1.incrementAndGet(); + } + }); + + Subscription s2 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(HystrixConfiguration configuration) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnNext : " + configuration); + payloads2.incrementAndGet(); + } + }); + //execute 1 command, then unsubscribe from first stream. then execute the rest + for (int i = 0; i < 50; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + if (i == 1) { + s1.unsubscribe(); + } + } + + assertTrue(latch1.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("s1 got : " + payloads1.get() + ", s2 got : " + payloads2.get()); + assertTrue("s1 got data", payloads1.get() > 0); + assertTrue("s2 got data", payloads2.get() > 0); + assertTrue("s1 got less data than s2", payloads2.get() > payloads1.get()); + } + + @Test + public void testTwoSubscribersBothUnsubscribe() throws Exception { + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final AtomicInteger payloads1 = new AtomicInteger(0); + final AtomicInteger payloads2 = new AtomicInteger(0); + + Subscription s1 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(HystrixConfiguration configuration) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnNext : " + configuration); + payloads1.incrementAndGet(); + } + }); + + Subscription s2 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(HystrixConfiguration configuration) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnNext : " + configuration); + payloads2.incrementAndGet(); + } + }); + //execute 2 commands, then unsubscribe from both streams, then execute the rest + for (int i = 0; i < 10; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + if (i == 2) { + s1.unsubscribe(); + s2.unsubscribe(); + } + } + assertFalse(stream.isSourceCurrentlySubscribed()); //both subscriptions have been cancelled - source should be too + + assertTrue(latch1.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("s1 got : " + payloads1.get() + ", s2 got : " + payloads2.get()); + assertTrue("s1 got data", payloads1.get() > 0); + assertTrue("s2 got data", payloads2.get() > 0); + } + + @Test + public void testTwoSubscribersOneSlowOneFast() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean foundError = new AtomicBoolean(false); + + Observable fast = stream + .observe() + .observeOn(Schedulers.newThread()); + Observable slow = stream + .observe() + .observeOn(Schedulers.newThread()) + .map(new Func1() { + @Override + public HystrixConfiguration call(HystrixConfiguration config) { + try { + Thread.sleep(100); + return config; + } catch (InterruptedException ex) { + return config; + } + } + }); + + Observable checkZippedEqual = Observable.zip(fast, slow, new Func2() { + @Override + public Boolean call(HystrixConfiguration payload, HystrixConfiguration payload2) { + return payload == payload2; + } + }); + + Subscription s1 = checkZippedEqual + .take(10000) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnError : " + e); + e.printStackTrace(); + foundError.set(true); + latch.countDown(); + } + + @Override + public void onNext(Boolean b) { + //System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnNext : " + b); + } + }); + + for (int i = 0; i < 50; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + } + + latch.await(10000, TimeUnit.MILLISECONDS); + assertFalse(foundError.get()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/CommandStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/CommandStreamTest.java new file mode 100644 index 0000000..0c269ba --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/CommandStreamTest.java @@ -0,0 +1,264 @@ +/** + * 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.metric; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import rx.functions.Func2; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class CommandStreamTest { + + static final AtomicInteger uniqueId = new AtomicInteger(0); + + public static class Command extends HystrixCommand { + + final String arg; + + final HystrixEventType executionResult; + final int executionLatency; + final HystrixEventType fallbackExecutionResult; + final int fallbackExecutionLatency; + + private Command(Setter setter, HystrixEventType executionResult, int executionLatency, String arg, + HystrixEventType fallbackExecutionResult, int fallbackExecutionLatency) { + super(setter); + this.executionResult = executionResult; + this.executionLatency = executionLatency; + this.fallbackExecutionResult = fallbackExecutionResult; + this.fallbackExecutionLatency = fallbackExecutionLatency; + this.arg = arg; + } + + public static Command from(HystrixCommandGroupKey groupKey, HystrixCommandKey key, HystrixEventType desiredEventType) { + return from(groupKey, key, desiredEventType, 0); + } + + public static Command from(HystrixCommandGroupKey groupKey, HystrixCommandKey key, HystrixEventType desiredEventType, int latency) { + return from(groupKey, key, desiredEventType, latency, HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); + } + + public static Command from(HystrixCommandGroupKey groupKey, HystrixCommandKey key, HystrixEventType desiredEventType, int latency, + HystrixEventType desiredFallbackEventType) { + return from(groupKey, key, desiredEventType, latency, HystrixCommandProperties.ExecutionIsolationStrategy.THREAD, desiredFallbackEventType); + } + + public static Command from(HystrixCommandGroupKey groupKey, HystrixCommandKey key, HystrixEventType desiredEventType, int latency, + HystrixEventType desiredFallbackEventType, int fallbackLatency) { + return from(groupKey, key, desiredEventType, latency, HystrixCommandProperties.ExecutionIsolationStrategy.THREAD, desiredFallbackEventType, fallbackLatency); + } + + public static Command from(HystrixCommandGroupKey groupKey, HystrixCommandKey key, HystrixEventType desiredEventType, int latency, + HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy) { + return from(groupKey, key, desiredEventType, latency, isolationStrategy, HystrixEventType.FALLBACK_SUCCESS, 0); + } + + public static Command from(HystrixCommandGroupKey groupKey, HystrixCommandKey key, HystrixEventType desiredEventType, int latency, + HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy, + HystrixEventType desiredFallbackEventType) { + return from(groupKey, key, desiredEventType, latency, isolationStrategy, desiredFallbackEventType, 0); + } + + public static Command from(HystrixCommandGroupKey groupKey, HystrixCommandKey key, HystrixEventType desiredEventType, int latency, + HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy, + HystrixEventType desiredFallbackEventType, int fallbackLatency) { + Setter setter = Setter.withGroupKey(groupKey) + .andCommandKey(key) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionTimeoutInMilliseconds(600) + .withExecutionIsolationStrategy(isolationStrategy) + .withCircuitBreakerEnabled(true) + .withCircuitBreakerRequestVolumeThreshold(3) + .withMetricsHealthSnapshotIntervalInMilliseconds(100) + .withMetricsRollingStatisticalWindowInMilliseconds(1000) + .withMetricsRollingStatisticalWindowBuckets(10) + .withRequestCacheEnabled(true) + .withRequestLogEnabled(true) + .withFallbackIsolationSemaphoreMaxConcurrentRequests(5)) + .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(groupKey.name())) + .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() + .withCoreSize(10) + .withMaxQueueSize(-1)); + + String uniqueArg; + + switch (desiredEventType) { + case SUCCESS: + uniqueArg = uniqueId.incrementAndGet() + ""; + return new Command(setter, HystrixEventType.SUCCESS, latency, uniqueArg, desiredFallbackEventType, 0); + case FAILURE: + uniqueArg = uniqueId.incrementAndGet() + ""; + return new Command(setter, HystrixEventType.FAILURE, latency, uniqueArg, desiredFallbackEventType, fallbackLatency); + case TIMEOUT: + uniqueArg = uniqueId.incrementAndGet() + ""; + return new Command(setter, HystrixEventType.SUCCESS, 700, uniqueArg, desiredFallbackEventType, fallbackLatency); + case BAD_REQUEST: + uniqueArg = uniqueId.incrementAndGet() + ""; + return new Command(setter, HystrixEventType.BAD_REQUEST, latency, uniqueArg, desiredFallbackEventType, 0); + case RESPONSE_FROM_CACHE: + String arg = uniqueId.get() + ""; + return new Command(setter, HystrixEventType.SUCCESS, 0, arg, desiredFallbackEventType, 0); + default: + throw new RuntimeException("not supported yet"); + } + } + + public static List getCommandsWithResponseFromCache(HystrixCommandGroupKey groupKey, HystrixCommandKey key) { + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + List cmds = new ArrayList(); + cmds.add(cmd1); + cmds.add(cmd2); + return cmds; + } + + @Override + protected Integer run() throws Exception { + try { + Thread.sleep(executionLatency); + switch (executionResult) { + case SUCCESS: + return 1; + case FAILURE: + throw new RuntimeException("induced failure"); + case BAD_REQUEST: + throw new HystrixBadRequestException("induced bad request"); + default: + throw new RuntimeException("unhandled HystrixEventType : " + executionResult); + } + } catch (InterruptedException ex) { + System.out.println("Received InterruptedException : " + ex); + throw ex; + } + } + + @Override + protected Integer getFallback() { + try { + Thread.sleep(fallbackExecutionLatency); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + switch (fallbackExecutionResult) { + case FALLBACK_SUCCESS: return -1; + case FALLBACK_FAILURE: throw new RuntimeException("induced failure"); + case FALLBACK_MISSING: throw new UnsupportedOperationException("fallback not defined"); + default: throw new RuntimeException("unhandled HystrixEventType : " + fallbackExecutionResult); + } + } + + @Override + protected String getCacheKey() { + return arg; + } + } + + public static class Collapser extends HystrixCollapser, Integer, Integer> { + private final Integer arg; + + public static Collapser from(Integer arg) { + return new Collapser(HystrixCollapserKey.Factory.asKey("Collapser"), arg); + } + + public static Collapser from(HystrixCollapserKey key, Integer arg) { + return new Collapser(key, arg); + } + + private Collapser(HystrixCollapserKey key, Integer arg) { + super(Setter.withCollapserKey(key) + .andCollapserPropertiesDefaults( + HystrixCollapserProperties.Setter() + .withTimerDelayInMilliseconds(100))); + this.arg = arg; + } + + @Override + public Integer getRequestArgument() { + return arg; + } + + @Override + protected HystrixCommand> createCommand(Collection> collapsedRequests) { + List args = new ArrayList(); + for (CollapsedRequest collapsedReq: collapsedRequests) { + args.add(collapsedReq.getArgument()); + } + return new BatchCommand(args); + } + + @Override + protected void mapResponseToRequests(List batchResponse, Collection> collapsedRequests) { + for (CollapsedRequest collapsedReq: collapsedRequests) { + collapsedReq.emitResponse(collapsedReq.getArgument()); + collapsedReq.setComplete(); + } + } + + @Override + protected String getCacheKey() { + return arg.toString(); + } + } + + private static class BatchCommand extends HystrixCommand> { + private List args; + + protected BatchCommand(List args) { + super(HystrixCommandGroupKey.Factory.asKey("BATCH")); + this.args = args; + } + + @Override + protected List run() throws Exception { + System.out.println(Thread.currentThread().getName() + " : Executing batch of : " + args.size()); + return args; + } + } + + protected static String bucketToString(long[] eventCounts) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (HystrixEventType eventType : HystrixEventType.values()) { + if (eventCounts[eventType.ordinal()] > 0) { + sb.append(eventType.name()).append("->").append(eventCounts[eventType.ordinal()]).append(", "); + } + } + sb.append("]"); + return sb.toString(); + } + + protected static boolean hasData(long[] eventCounts) { + for (HystrixEventType eventType : HystrixEventType.values()) { + if (eventCounts[eventType.ordinal()] > 0) { + return true; + } + } + return false; + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/HystrixCommandCompletionStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/HystrixCommandCompletionStreamTest.java new file mode 100644 index 0000000..dbfc5d0 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/HystrixCommandCompletionStreamTest.java @@ -0,0 +1,103 @@ +/** + * 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.metric; + +import com.netflix.hystrix.ExecutionResult; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import org.junit.Test; +import rx.Subscriber; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class HystrixCommandCompletionStreamTest { + + private Subscriber getLatchedSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + e.printStackTrace(); + latch.countDown(); + } + + @Override + public void onNext(T value) { + System.out.println("OnNext : " + value); + } + }; + } + + static final HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("COMMAND"); + static final HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool"); + final HystrixCommandCompletionStream commandStream = new HystrixCommandCompletionStream(commandKey); + + @Test + public void noEvents() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + Subscriber subscriber = getLatchedSubscriber(latch); + + commandStream.observe().take(1).subscribe(subscriber); + + //no writes + + assertFalse(latch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSingleWriteSingleSubscriber() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + Subscriber subscriber = getLatchedSubscriber(latch); + + commandStream.observe().take(1).subscribe(subscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.SUCCESS).setExecutedInThread(); + HystrixCommandCompletion event = HystrixCommandCompletion.from(result, commandKey, threadPoolKey); + commandStream.write(event); + + assertTrue(latch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSingleWriteMultipleSubscribers() throws InterruptedException { + CountDownLatch latch1 = new CountDownLatch(1); + Subscriber subscriber1 = getLatchedSubscriber(latch1); + + CountDownLatch latch2 = new CountDownLatch(1); + Subscriber subscriber2 = getLatchedSubscriber(latch2); + + commandStream.observe().take(1).subscribe(subscriber1); + commandStream.observe().take(1).subscribe(subscriber2); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.SUCCESS).setExecutedInThread(); + HystrixCommandCompletion event = HystrixCommandCompletion.from(result, commandKey, threadPoolKey); + commandStream.write(event); + + assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(10, TimeUnit.MILLISECONDS)); + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/HystrixThreadEventStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/HystrixThreadEventStreamTest.java new file mode 100644 index 0000000..5438fe7 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/HystrixThreadEventStreamTest.java @@ -0,0 +1,345 @@ +/** + * 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.metric; + +import com.netflix.hystrix.ExecutionResult; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import org.junit.Test; +import rx.Subscriber; +import rx.functions.Action1; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class HystrixThreadEventStreamTest { + + HystrixCommandKey commandKey; + HystrixThreadPoolKey threadPoolKey; + + HystrixThreadEventStream writeToStream; + HystrixCommandCompletionStream readCommandStream; + HystrixThreadPoolCompletionStream readThreadPoolStream; + + public HystrixThreadEventStreamTest() { + commandKey = HystrixCommandKey.Factory.asKey("CMD-ThreadStream"); + threadPoolKey = HystrixThreadPoolKey.Factory.asKey("TP-ThreadStream"); + + writeToStream = HystrixThreadEventStream.getInstance(); + readCommandStream = HystrixCommandCompletionStream.getInstance(commandKey); + readThreadPoolStream = HystrixThreadPoolCompletionStream.getInstance(threadPoolKey); + } + + private Subscriber getLatchedSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + e.printStackTrace(); + latch.countDown(); + } + + @Override + public void onNext(T value) { + System.out.println("OnNext : " + value); + } + }; + } + + @Test + public void noEvents() throws InterruptedException { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + //no writes + + assertFalse(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertFalse(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testThreadIsolatedSuccess() throws InterruptedException { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.SUCCESS).setExecutedInThread(); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSemaphoreIsolatedSuccess() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.SUCCESS); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertFalse(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testThreadIsolatedFailure() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.FAILURE).setExecutedInThread(); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSemaphoreIsolatedFailure() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.FAILURE); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertFalse(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testThreadIsolatedTimeout() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.TIMEOUT).setExecutedInThread(); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSemaphoreIsolatedTimeout() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.TIMEOUT); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertFalse(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testThreadIsolatedBadRequest() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.BAD_REQUEST).setExecutedInThread(); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSemaphoreIsolatedBadRequest() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.BAD_REQUEST); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertFalse(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testThreadRejectedCommand() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.THREAD_POOL_REJECTED); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSemaphoreRejectedCommand() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.SEMAPHORE_REJECTED); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertFalse(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testThreadIsolatedResponseFromCache() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber> commandListSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().buffer(500, TimeUnit.MILLISECONDS).take(1) + .doOnNext(new Action1>() { + @Override + public void call(List hystrixCommandCompletions) { + System.out.println("LIST : " + hystrixCommandCompletions); + assertEquals(3, hystrixCommandCompletions.size()); + } + }) + .subscribe(commandListSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.SUCCESS).setExecutedInThread(); + ExecutionResult cache1 = ExecutionResult.from(HystrixEventType.RESPONSE_FROM_CACHE); + ExecutionResult cache2 = ExecutionResult.from(HystrixEventType.RESPONSE_FROM_CACHE); + writeToStream.executionDone(result, commandKey, threadPoolKey); + writeToStream.executionDone(cache1, commandKey, threadPoolKey); + writeToStream.executionDone(cache2, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertTrue(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSemaphoreIsolatedResponseFromCache() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber> commandListSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().buffer(500, TimeUnit.MILLISECONDS).take(1) + .doOnNext(new Action1>() { + @Override + public void call(List hystrixCommandCompletions) { + System.out.println("LIST : " + hystrixCommandCompletions); + assertEquals(3, hystrixCommandCompletions.size()); + } + }) + .subscribe(commandListSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.SUCCESS); + ExecutionResult cache1 = ExecutionResult.from(HystrixEventType.RESPONSE_FROM_CACHE); + ExecutionResult cache2 = ExecutionResult.from(HystrixEventType.RESPONSE_FROM_CACHE); + writeToStream.executionDone(result, commandKey, threadPoolKey); + writeToStream.executionDone(cache1, commandKey, threadPoolKey); + writeToStream.executionDone(cache2, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertFalse(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testShortCircuit() throws Exception { + CountDownLatch commandLatch = new CountDownLatch(1); + CountDownLatch threadPoolLatch = new CountDownLatch(1); + + Subscriber commandSubscriber = getLatchedSubscriber(commandLatch); + readCommandStream.observe().take(1).subscribe(commandSubscriber); + + Subscriber threadPoolSubscriber = getLatchedSubscriber(threadPoolLatch); + readThreadPoolStream.observe().take(1).subscribe(threadPoolSubscriber); + + ExecutionResult result = ExecutionResult.from(HystrixEventType.SHORT_CIRCUITED); + writeToStream.executionDone(result, commandKey, threadPoolKey); + + assertTrue(commandLatch.await(1000, TimeUnit.MILLISECONDS)); + assertFalse(threadPoolLatch.await(1000, TimeUnit.MILLISECONDS)); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeCollapserEventCounterStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeCollapserEventCounterStreamTest.java new file mode 100644 index 0000000..591ba1f --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeCollapserEventCounterStreamTest.java @@ -0,0 +1,189 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class CumulativeCollapserEventCounterStreamTest extends CommandStreamTest { + HystrixRequestContext context; + CumulativeCollapserEventCounterStream stream; + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(long[] eventCounts) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : " + collapserEventsToStr(eventCounts)); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + CumulativeCollapserEventCounterStream.reset(); + } + + protected static String collapserEventsToStr(long[] eventCounts) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (HystrixEventType.Collapser eventType : HystrixEventType.Collapser.values()) { + if (eventCounts[eventType.ordinal()] > 0) { + sb.append(eventType.name()).append("->").append(eventCounts[eventType.ordinal()]).append(", "); + } + } + sb.append("]"); + return sb.toString(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("CumulativeCollapser-A"); + stream = CumulativeCollapserEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(HystrixEventType.Collapser.values().length, stream.getLatest().length); + assertEquals(0, stream.getLatest(HystrixEventType.Collapser.ADDED_TO_BATCH)); + assertEquals(0, stream.getLatest(HystrixEventType.Collapser.BATCH_EXECUTED)); + assertEquals(0, stream.getLatest(HystrixEventType.Collapser.RESPONSE_FROM_CACHE)); + } + + + @Test + public void testCollapsed() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("CumulativeCollapser-B"); + stream = CumulativeCollapserEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + for (int i = 0; i < 3; i++) { + CommandStreamTest.Collapser.from(key, i).observe(); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.Collapser.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.Collapser.values().length]; + expected[HystrixEventType.Collapser.BATCH_EXECUTED.ordinal()] = 1; + expected[HystrixEventType.Collapser.ADDED_TO_BATCH.ordinal()] = 3; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testCollapsedAndResponseFromCache() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("CumulativeCollapser-C"); + stream = CumulativeCollapserEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + for (int i = 0; i < 3; i++) { + CommandStreamTest.Collapser.from(key, i).observe(); + CommandStreamTest.Collapser.from(key, i).observe(); //same arg - should get a response from cache + CommandStreamTest.Collapser.from(key, i).observe(); //same arg - should get a response from cache + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.Collapser.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.Collapser.values().length]; + expected[HystrixEventType.Collapser.BATCH_EXECUTED.ordinal()] = 1; + expected[HystrixEventType.Collapser.ADDED_TO_BATCH.ordinal()] = 3; + expected[HystrixEventType.Collapser.RESPONSE_FROM_CACHE.ordinal()] = 6; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + //by doing a take(30), we expect all values to stay in the stream, as cumulative counters never age out of window + @Test + public void testCollapsedAndResponseFromCacheAgeOutOfCumulativeWindow() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("CumulativeCollapser-D"); + stream = CumulativeCollapserEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(getSubscriber(latch)); + + for (int i = 0; i < 3; i++) { + CommandStreamTest.Collapser.from(key, i).observe(); + CommandStreamTest.Collapser.from(key, i).observe(); //same arg - should get a response from cache + CommandStreamTest.Collapser.from(key, i).observe(); //same arg - should get a response from cache + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.Collapser.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.Collapser.values().length]; + expected[HystrixEventType.Collapser.BATCH_EXECUTED.ordinal()] = 1; + expected[HystrixEventType.Collapser.ADDED_TO_BATCH.ordinal()] = 3; + expected[HystrixEventType.Collapser.RESPONSE_FROM_CACHE.ordinal()] = 6; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeCommandEventCounterStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeCommandEventCounterStreamTest.java new file mode 100644 index 0000000..d5207a5 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeCommandEventCounterStreamTest.java @@ -0,0 +1,590 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class CumulativeCommandEventCounterStreamTest extends CommandStreamTest { + HystrixRequestContext context; + CumulativeCommandEventCounterStream stream; + + private static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("CumulativeCommandCounter"); + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(long[] eventCounts) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : " + bucketToString(eventCounts)); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + CumulativeCommandEventCounterStream.reset(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-A"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + assertFalse(hasData(stream.getLatest())); + } + + @Test + public void testSingleSuccess() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-B"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd = Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testSingleFailure() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-C"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd = Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testSingleTimeout() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-D"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd = Command.from(groupKey, key, HystrixEventType.TIMEOUT); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.TIMEOUT.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testSingleBadRequest() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-E"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd = Command.from(groupKey, key, HystrixEventType.BAD_REQUEST); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.BAD_REQUEST.ordinal()] = 1; + expected[HystrixEventType.EXCEPTION_THROWN.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testRequestFromCache() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-F"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + + cmd1.observe(); + cmd2.observe(); + cmd3.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 1; + expected[HystrixEventType.RESPONSE_FROM_CACHE.ordinal()] = 2; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testShortCircuited() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-G"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //3 failures in a row will trip circuit. let bucket roll once then submit 2 requests. + //should see 3 FAILUREs and 2 SHORT_CIRCUITs and then 5 FALLBACK_SUCCESSes + + Command failure1 = Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + Command failure2 = Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + Command failure3 = Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + Command shortCircuit1 = Command.from(groupKey, key, HystrixEventType.SUCCESS); + Command shortCircuit2 = Command.from(groupKey, key, HystrixEventType.SUCCESS); + + failure1.observe(); + failure2.observe(); + failure3.observe(); + + try { + Thread.sleep(500); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + shortCircuit1.observe(); + shortCircuit2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(shortCircuit1.isResponseShortCircuited()); + assertTrue(shortCircuit2.isResponseShortCircuited()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 3; + expected[HystrixEventType.SHORT_CIRCUITED.ordinal()] = 2; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 5; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testSemaphoreRejected() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-H"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //10 commands will saturate semaphore when called from different threads. + //submit 2 more requests and they should be SEMAPHORE_REJECTED + //should see 10 SUCCESSes, 2 SEMAPHORE_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 500, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + Command rejected1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + Command rejected2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + + for (final Command saturator : saturators) { + new Thread(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + saturator.observe(); + } + })).start(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseSemaphoreRejected()); + assertTrue(rejected2.isResponseSemaphoreRejected()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 10; + expected[HystrixEventType.SEMAPHORE_REJECTED.ordinal()] = 2; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 2; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testThreadPoolRejected() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-I"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //10 commands will saturate threadpools when called concurrently. + //submit 2 more requests and they should be THREADPOOL_REJECTED + //should see 10 SUCCESSes, 2 THREADPOOL_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 500)); + } + + Command rejected1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + Command rejected2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + + for (final Command saturator : saturators) { + saturator.observe(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseThreadPoolRejected()); + assertTrue(rejected2.isResponseThreadPoolRejected()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 10; + expected[HystrixEventType.THREAD_POOL_REJECTED.ordinal()] = 2; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 2; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testFallbackFailure() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-J"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd = Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_FAILURE); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_FAILURE.ordinal()] = 1; + expected[HystrixEventType.EXCEPTION_THROWN.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testFallbackMissing() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-K"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd = Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_MISSING); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_MISSING.ordinal()] = 1; + expected[HystrixEventType.EXCEPTION_THROWN.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testFallbackRejection() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-L"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //fallback semaphore size is 5. So let 5 commands saturate that semaphore, then + //let 2 more commands go to fallback. they should get rejected by the fallback-semaphore + + List fallbackSaturators = new ArrayList(); + for (int i = 0; i < 5; i++) { + fallbackSaturators.add(Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 400)); + } + + Command rejection1 = Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + Command rejection2 = Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + + for (Command saturator: fallbackSaturators) { + saturator.observe(); + } + + try { + Thread.sleep(70); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + rejection1.observe(); + rejection2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 7; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 5; + expected[HystrixEventType.FALLBACK_REJECTION.ordinal()] = 2; + expected[HystrixEventType.EXCEPTION_THROWN.ordinal()] = 2; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testCancelled() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-M"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command toCancel = Command.from(groupKey, key, HystrixEventType.SUCCESS, 500); + + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : about to observe and subscribe"); + Subscription s = toCancel.observe(). + doOnUnsubscribe(new Action0() { + @Override + public void call() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : UnSubscribe from command.observe()"); + } + }). + subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("Command OnCompleted"); + } + + @Override + public void onError(Throwable e) { + System.out.println("Command OnError : " + e); + } + + @Override + public void onNext(Integer i) { + System.out.println("Command OnNext : " + i); + } + }); + + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : about to unsubscribe"); + s.unsubscribe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.CANCELLED.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testCollapsed() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("BatchCommand"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + for (int i = 0; i < 3; i++) { + CommandStreamTest.Collapser.from(i).observe(); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 1; + expected[HystrixEventType.COLLAPSED.ordinal()] = 3; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testMultipleEventsOverTimeGetStoredAndNeverAgeOut() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-CumulativeCounter-N"); + stream = CumulativeCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + //by doing a take(30), we ensure that no rolling out of window takes place + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.FAILURE, 10); + + cmd1.observe(); + cmd2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 1; + expected[HystrixEventType.FAILURE.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeThreadPoolEventCounterStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeThreadPoolEventCounterStreamTest.java new file mode 100644 index 0000000..f8cc9a6 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/CumulativeThreadPoolEventCounterStreamTest.java @@ -0,0 +1,518 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class CumulativeThreadPoolEventCounterStreamTest extends CommandStreamTest { + HystrixRequestContext context; + CumulativeThreadPoolEventCounterStream stream; + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(long[] eventCounts) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : " + eventCounts[0] + " : " + eventCounts[1]); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + RollingCommandEventCounterStream.reset(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-A"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-A"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-A"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSingleSuccess() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-B"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-B"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-B"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSingleFailure() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-C"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-C"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-C"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSingleTimeout() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-D"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-D"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-D"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.TIMEOUT); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSingleBadRequest() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-E"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-E"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-E"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.BAD_REQUEST); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testRequestFromCache() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-F"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-F"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-F"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + CommandStreamTest.Command cmd2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + CommandStreamTest.Command cmd3 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + + cmd1.observe(); + cmd2.observe(); + cmd3.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + + //RESPONSE_FROM_CACHE should not show up at all in thread pool counters - just the success + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testShortCircuited() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-G"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-G"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-G"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //3 failures in a row will trip circuit. let bucket roll once then submit 2 requests. + //should see 3 FAILUREs and 2 SHORT_CIRCUITs and each should see a FALLBACK_SUCCESS + + CommandStreamTest.Command failure1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + CommandStreamTest.Command failure2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + CommandStreamTest.Command failure3 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + CommandStreamTest.Command shortCircuit1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS); + CommandStreamTest.Command shortCircuit2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS); + + failure1.observe(); + failure2.observe(); + failure3.observe(); + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + shortCircuit1.observe(); + shortCircuit2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(shortCircuit1.isResponseShortCircuited()); + assertTrue(shortCircuit2.isResponseShortCircuited()); + + //only the FAILUREs should show up in thread pool counters + assertEquals(2, stream.getLatest().length); + assertEquals(3, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSemaphoreRejected() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-H"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-H"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-H"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands will saturate semaphore when called from different threads. + //submit 2 more requests and they should be SEMAPHORE_REJECTED + //should see 10 SUCCESSes, 2 SEMAPHORE_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 300, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + CommandStreamTest.Command rejected1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + CommandStreamTest.Command rejected2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + + for (final CommandStreamTest.Command saturator : saturators) { + new Thread(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + saturator.observe(); + } + })).start(); + } + + try { + Thread.sleep(10); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseSemaphoreRejected()); + assertTrue(rejected2.isResponseSemaphoreRejected()); + + //none of these got executed on a thread-pool, so thread pool metrics should be 0 + assertEquals(2, stream.getLatest().length); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testThreadPoolRejected() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-I"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-I"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-I"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands will saturate threadpools when called concurrently. + //submit 2 more requests and they should be THREADPOOL_REJECTED + //should see 10 SUCCESSes, 2 THREADPOOL_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 700)); + } + + CommandStreamTest.Command rejected1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + CommandStreamTest.Command rejected2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + + for (final CommandStreamTest.Command saturator : saturators) { + saturator.observe(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseThreadPoolRejected()); + assertTrue(rejected2.isResponseThreadPoolRejected()); + + //all 12 commands got submitted to thread pool, 10 accepted, 2 rejected is expected + assertEquals(2, stream.getLatest().length); + assertEquals(10, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(2, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testFallbackFailure() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-J"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-J"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-J"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_FAILURE); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testFallbackMissing() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-K"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-K"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-K"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_MISSING); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testFallbackRejection() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-L"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-L"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-L"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //fallback semaphore size is 5. So let 5 commands saturate that semaphore, then + //let 2 more commands go to fallback. they should get rejected by the fallback-semaphore + + List fallbackSaturators = new ArrayList(); + for (int i = 0; i < 5; i++) { + fallbackSaturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 400)); + } + + CommandStreamTest.Command rejection1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + CommandStreamTest.Command rejection2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + + for (CommandStreamTest.Command saturator: fallbackSaturators) { + saturator.observe(); + } + + try { + Thread.sleep(70); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + rejection1.observe(); + rejection2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + //all 7 commands executed on-thread, so should be executed according to thread-pool metrics + assertEquals(2, stream.getLatest().length); + assertEquals(7, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + //in a rolling window, take(30) would age out all counters. in the cumulative count, we expect them to remain non-zero forever + @Test + public void testMultipleEventsOverTimeGetStoredAndDoNotAgeOut() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Cumulative-ThreadPool-M"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("Cumulative-ThreadPool-M"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cumulative-Counter-M"); + stream = CumulativeThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + CommandStreamTest.Command cmd2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 10); + + cmd1.observe(); + cmd2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + //all commands should have aged out + assertEquals(2, stream.getLatest().length); + assertEquals(2, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/HealthCountsStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/HealthCountsStreamTest.java new file mode 100644 index 0000000..83321ea --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/HealthCountsStreamTest.java @@ -0,0 +1,622 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func2; +import rx.schedulers.Schedulers; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + +public class HealthCountsStreamTest extends CommandStreamTest { + HystrixRequestContext context; + HealthCountsStream stream; + + static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("HealthCounts"); + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(HystrixCommandMetrics.HealthCounts healthCounts) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : " + healthCounts); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + RollingCommandEventCounterStream.reset(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-A"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0L, stream.getLatest().getErrorCount()); + assertEquals(0L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testSingleSuccess() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-B"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0L, stream.getLatest().getErrorCount()); + assertEquals(1L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testSingleFailure() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-C"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1L, stream.getLatest().getErrorCount()); + assertEquals(1L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testSingleTimeout() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-D"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.TIMEOUT); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1L, stream.getLatest().getErrorCount()); + assertEquals(1L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testSingleBadRequest() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-E"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.BAD_REQUEST); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0L, stream.getLatest().getErrorCount()); + assertEquals(0L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testRequestFromCache() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-F"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + CommandStreamTest.Command cmd2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + CommandStreamTest.Command cmd3 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + + cmd1.observe(); + cmd2.observe(); + cmd3.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0L, stream.getLatest().getErrorCount()); + assertEquals(1L, stream.getLatest().getTotalRequests()); //responses from cache should not show up here + } + + @Test + public void testShortCircuited() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-G"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //3 failures in a row will trip circuit. let bucket roll once then submit 2 requests. + //should see 3 FAILUREs and 2 SHORT_CIRCUITs and then 5 FALLBACK_SUCCESSes + + CommandStreamTest.Command failure1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + CommandStreamTest.Command failure2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + CommandStreamTest.Command failure3 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + CommandStreamTest.Command shortCircuit1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS); + CommandStreamTest.Command shortCircuit2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS); + + failure1.observe(); + failure2.observe(); + failure3.observe(); + + try { + Thread.sleep(500); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + shortCircuit1.observe(); + shortCircuit2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + assertTrue(shortCircuit1.isResponseShortCircuited()); + assertTrue(shortCircuit2.isResponseShortCircuited()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + //should only see failures here, not SHORT-CIRCUITS + assertEquals(3L, stream.getLatest().getErrorCount()); + assertEquals(3L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testSemaphoreRejected() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-H"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands will saturate semaphore when called from different threads. + //submit 2 more requests and they should be SEMAPHORE_REJECTED + //should see 10 SUCCESSes, 2 SEMAPHORE_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 400, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + CommandStreamTest.Command rejected1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + CommandStreamTest.Command rejected2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + + for (final CommandStreamTest.Command saturator : saturators) { + new Thread(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + saturator.observe(); + } + })).start(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseSemaphoreRejected()); + assertTrue(rejected2.isResponseSemaphoreRejected()); + assertEquals(2L, stream.getLatest().getErrorCount()); + assertEquals(12L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testThreadPoolRejected() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-I"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands will saturate threadpools when called concurrently. + //submit 2 more requests and they should be THREADPOOL_REJECTED + //should see 10 SUCCESSes, 2 THREADPOOL_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 400)); + } + + CommandStreamTest.Command rejected1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + CommandStreamTest.Command rejected2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + + for (final CommandStreamTest.Command saturator : saturators) { + saturator.observe(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseThreadPoolRejected()); + assertTrue(rejected2.isResponseThreadPoolRejected()); + assertEquals(2L, stream.getLatest().getErrorCount()); + assertEquals(12L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testFallbackFailure() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-J"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_FAILURE); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1L, stream.getLatest().getErrorCount()); + assertEquals(1L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testFallbackMissing() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-K"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_MISSING); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(1L, stream.getLatest().getErrorCount()); + assertEquals(1L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testFallbackRejection() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-L"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //fallback semaphore size is 5. So let 5 commands saturate that semaphore, then + //let 2 more commands go to fallback. they should get rejected by the fallback-semaphore + + List fallbackSaturators = new ArrayList(); + for (int i = 0; i < 5; i++) { + fallbackSaturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 400)); + } + + CommandStreamTest.Command rejection1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + CommandStreamTest.Command rejection2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + + for (CommandStreamTest.Command saturator: fallbackSaturators) { + saturator.observe(); + } + + try { + Thread.sleep(70); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + rejection1.observe(); + rejection2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(7L, stream.getLatest().getErrorCount()); + assertEquals(7L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testMultipleEventsOverTimeGetStoredAndAgeOut() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-M"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + //by doing a take(30), we ensure that all rolling counts go back to 0 + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + CommandStreamTest.Command cmd2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 10); + + cmd1.observe(); + cmd2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(0L, stream.getLatest().getErrorCount()); + assertEquals(0L, stream.getLatest().getTotalRequests()); + } + + @Test + public void testSharedSourceStream() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-N"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean allEqual = new AtomicBoolean(false); + + Observable o1 = stream + .observe() + .take(10) + .observeOn(Schedulers.computation()); + + Observable o2 = stream + .observe() + .take(10) + .observeOn(Schedulers.computation()); + + Observable zipped = Observable.zip(o1, o2, new Func2() { + @Override + public Boolean call(HystrixCommandMetrics.HealthCounts healthCounts, HystrixCommandMetrics.HealthCounts healthCounts2) { + return healthCounts == healthCounts2; //we want object equality + } + }); + Observable < Boolean > reduced = zipped.reduce(true, new Func2() { + @Override + public Boolean call(Boolean a, Boolean b) { + return a && b; + } + }); + + reduced.subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Reduced OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Reduced OnError : " + e); + e.printStackTrace(); + latch.countDown(); + } + + @Override + public void onNext(Boolean b) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Reduced OnNext : " + b); + allEqual.set(b); + } + }); + + for (int i = 0; i < 10; i++) { + HystrixCommand cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + cmd.execute(); + } + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(allEqual.get()); + //we should be getting the same object from both streams. this ensures that multiple subscribers don't induce extra work + } + + @Test + public void testTwoSubscribersOneUnsubscribes() throws Exception { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Health-O"); + stream = HealthCountsStream.getInstance(key, 10, 100); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final AtomicInteger healthCounts1 = new AtomicInteger(0); + final AtomicInteger healthCounts2 = new AtomicInteger(0); + + Subscription s1 = stream + .observe() + .take(10) + .observeOn(Schedulers.computation()) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Health 1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Health 1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(HystrixCommandMetrics.HealthCounts healthCounts) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Health 1 OnNext : " + healthCounts); + healthCounts1.incrementAndGet(); + } + }); + + Subscription s2 = stream + .observe() + .take(10) + .observeOn(Schedulers.computation()) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Health 2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Health 2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(HystrixCommandMetrics.HealthCounts healthCounts) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Health 2 OnNext : " + healthCounts + " : " + healthCounts2.get()); + healthCounts2.incrementAndGet(); + } + }); + //execute 5 commands, then unsubscribe from first stream. then execute the rest + for (int i = 0; i < 10; i++) { + HystrixCommand cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + cmd.execute(); + if (i == 5) { + s1.unsubscribe(); + } + } + assertTrue(stream.isSourceCurrentlySubscribed()); //only 1/2 subscriptions has been cancelled + + assertTrue(latch1.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("s1 got : " + healthCounts1.get() + ", s2 got : " + healthCounts2.get()); + assertTrue("s1 got data", healthCounts1.get() > 0); + assertTrue("s2 got data", healthCounts2.get() > 0); + assertTrue("s1 got less data than s2", healthCounts2.get() > healthCounts1.get()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/HystrixDashboardStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/HystrixDashboardStreamTest.java new file mode 100644 index 0000000..dc95009 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/HystrixDashboardStreamTest.java @@ -0,0 +1,322 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.CommandStreamTest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.schedulers.Schedulers; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class HystrixDashboardStreamTest extends CommandStreamTest { + + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + HystrixDashboardStream stream; + private final static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Dashboard"); + private final static HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("DashboardCommand"); + + @Before + public void init() { + stream = HystrixDashboardStream.getNonSingletonInstanceOnlyUsedInUnitTests(10); + } + + @Test + public void testStreamHasData() throws Exception { + final AtomicBoolean commandShowsUp = new AtomicBoolean(false); + final CountDownLatch latch = new CountDownLatch(1); + final int NUM = 10; + + for (int i = 0; i < 2; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.observe(); + } + + stream.observe().take(NUM).subscribe( + new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnError : " + e); + latch.countDown(); + } + + @Override + public void onNext(HystrixDashboardStream.DashboardData dashboardData) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Received data with : " + dashboardData.commandMetrics.size() + " commands"); + for (HystrixCommandMetrics metrics : dashboardData.commandMetrics) { + if (metrics.getCommandKey().equals(commandKey)) { + commandShowsUp.set(true); + } + } + } + }); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(commandShowsUp.get()); + } + + @Test + public void testTwoSubscribersOneUnsubscribes() throws Exception { + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final AtomicInteger payloads1 = new AtomicInteger(0); + final AtomicInteger payloads2 = new AtomicInteger(0); + + Subscription s1 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(HystrixDashboardStream.DashboardData dashboardData) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnNext : " + dashboardData); + payloads1.incrementAndGet(); + } + }); + + Subscription s2 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(HystrixDashboardStream.DashboardData dashboardData) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnNext : " + dashboardData); + payloads2.incrementAndGet(); + } + }); + //execute 1 command, then unsubscribe from first stream. then execute the rest + for (int i = 0; i < 50; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + if (i == 1) { + s1.unsubscribe(); + } + } + + assertTrue(latch1.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("s1 got : " + payloads1.get() + ", s2 got : " + payloads2.get()); + assertTrue("s1 got data", payloads1.get() > 0); + assertTrue("s2 got data", payloads2.get() > 0); + assertTrue("s1 got less data than s2", payloads2.get() > payloads1.get()); + } + + @Test + public void testTwoSubscribersBothUnsubscribe() throws Exception { + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final AtomicInteger payloads1 = new AtomicInteger(0); + final AtomicInteger payloads2 = new AtomicInteger(0); + + Subscription s1 = stream + .observe() + .take(10) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(HystrixDashboardStream.DashboardData dashboardData) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnNext : " + dashboardData); + payloads1.incrementAndGet(); + } + }); + + Subscription s2 = stream + .observe() + .take(10) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(HystrixDashboardStream.DashboardData dashboardData) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnNext : " + dashboardData); + payloads2.incrementAndGet(); + } + }); + //execute half the commands, then unsubscribe from both streams, then execute the rest + for (int i = 0; i < 50; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + if (i == 25) { + s1.unsubscribe(); + s2.unsubscribe(); + } + } + assertFalse(stream.isSourceCurrentlySubscribed()); //both subscriptions have been cancelled - source should be too + + assertTrue(latch1.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("s1 got : " + payloads1.get() + ", s2 got : " + payloads2.get()); + assertTrue("s1 got data", payloads1.get() > 0); + assertTrue("s2 got data", payloads2.get() > 0); + } + + @Test + public void testTwoSubscribersOneSlowOneFast() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean foundError = new AtomicBoolean(false); + + Observable fast = stream + .observe() + .observeOn(Schedulers.newThread()); + Observable slow = stream + .observe() + .observeOn(Schedulers.newThread()) + .map(new Func1() { + @Override + public HystrixDashboardStream.DashboardData call(HystrixDashboardStream.DashboardData n) { + try { + Thread.sleep(100); + return n; + } catch (InterruptedException ex) { + return n; + } + } + }); + + Observable checkZippedEqual = Observable.zip(fast, slow, new Func2() { + @Override + public Boolean call(HystrixDashboardStream.DashboardData payload, HystrixDashboardStream.DashboardData payload2) { + return payload == payload2; + } + }); + + Subscription s1 = checkZippedEqual + .take(10000) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnError : " + e); + e.printStackTrace(); + foundError.set(true); + latch.countDown(); + } + + @Override + public void onNext(Boolean b) { + //System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnNext : " + b); + } + }); + + for (int i = 0; i < 50; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + } + + latch.await(10000, TimeUnit.MILLISECONDS); + assertFalse(foundError.get()); + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCollapserBatchSizeDistributionStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCollapserBatchSizeDistributionStreamTest.java new file mode 100644 index 0000000..6b36f7c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCollapserBatchSizeDistributionStreamTest.java @@ -0,0 +1,223 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.metric.CachedValuesHistogram; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class RollingCollapserBatchSizeDistributionStreamTest extends CommandStreamTest { + RollingCollapserBatchSizeDistributionStream stream; + HystrixRequestContext context; + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + stream.unsubscribe(); + context.shutdown(); + RollingCollapserBatchSizeDistributionStream.reset(); + } + + @Test + public void testEmptyStreamProducesEmptyDistributions() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("Collapser-Batch-Size-A"); + stream = RollingCollapserBatchSizeDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().skip(10).take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println("OnNext @ " + System.currentTimeMillis()); + assertEquals(0, distribution.getTotalCount()); + } + }); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(0, stream.getLatest().getTotalCount()); + } + + @Test + public void testBatches() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("Collapser-Batch-Size-B"); + stream = RollingCollapserBatchSizeDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println("OnNext @ " + System.currentTimeMillis()); + } + }); + + Collapser.from(key, 1).observe(); + Collapser.from(key, 2).observe(); + Collapser.from(key, 3).observe(); + + try { + Thread.sleep(250); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + Collapser.from(key, 4).observe(); + + try { + Thread.sleep(250); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + Collapser.from(key, 5).observe(); + Collapser.from(key, 6).observe(); + Collapser.from(key, 7).observe(); + Collapser.from(key, 8).observe(); + Collapser.from(key, 9).observe(); + + try { + Thread.sleep(250); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + Collapser.from(key, 10).observe(); + Collapser.from(key, 11).observe(); + Collapser.from(key, 12).observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + //should have 4 batches: 3, 1, 5, 3 + assertEquals(4, stream.getLatest().getTotalCount()); + assertEquals(3, stream.getLatestMean()); + assertEquals(1, stream.getLatestPercentile(0)); + assertEquals(5, stream.getLatestPercentile(100)); + } + + //by doing a take(30), all metrics should fall out of window and we should observe an empty histogram + @Test + public void testBatchesAgeOut() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("Collapser-Batch-Size-B"); + stream = RollingCollapserBatchSizeDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println("OnNext @ " + System.currentTimeMillis()); + } + }); + + Collapser.from(key, 1).observe(); + Collapser.from(key, 2).observe(); + Collapser.from(key, 3).observe(); + + try { + Thread.sleep(200); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + Collapser.from(key, 4).observe(); + + try { + Thread.sleep(200); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + Collapser.from(key, 5).observe(); + Collapser.from(key, 6).observe(); + Collapser.from(key, 7).observe(); + Collapser.from(key, 8).observe(); + Collapser.from(key, 9).observe(); + + try { + Thread.sleep(200); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + Collapser.from(key, 10).observe(); + Collapser.from(key, 11).observe(); + Collapser.from(key, 12).observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + assertEquals(0, stream.getLatest().getTotalCount()); + assertEquals(0, stream.getLatestMean()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCollapserEventCounterStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCollapserEventCounterStreamTest.java new file mode 100644 index 0000000..e6e3955 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCollapserEventCounterStreamTest.java @@ -0,0 +1,189 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RollingCollapserEventCounterStreamTest extends CommandStreamTest { + HystrixRequestContext context; + RollingCollapserEventCounterStream stream; + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(long[] eventCounts) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : " + collapserEventsToStr(eventCounts)); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + RollingCollapserEventCounterStream.reset(); + } + + protected static String collapserEventsToStr(long[] eventCounts) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (HystrixEventType.Collapser eventType : HystrixEventType.Collapser.values()) { + if (eventCounts[eventType.ordinal()] > 0) { + sb.append(eventType.name()).append("->").append(eventCounts[eventType.ordinal()]).append(", "); + } + } + sb.append("]"); + return sb.toString(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("RollingCollapser-A"); + stream = RollingCollapserEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(HystrixEventType.Collapser.values().length, stream.getLatest().length); + assertEquals(0, stream.getLatest(HystrixEventType.Collapser.ADDED_TO_BATCH)); + assertEquals(0, stream.getLatest(HystrixEventType.Collapser.BATCH_EXECUTED)); + assertEquals(0, stream.getLatest(HystrixEventType.Collapser.RESPONSE_FROM_CACHE)); + } + + + @Test + public void testCollapsed() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("RollingCollapser-B"); + stream = RollingCollapserEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + for (int i = 0; i < 3; i++) { + CommandStreamTest.Collapser.from(key, i).observe(); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.Collapser.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.Collapser.values().length]; + expected[HystrixEventType.Collapser.BATCH_EXECUTED.ordinal()] = 1; + expected[HystrixEventType.Collapser.ADDED_TO_BATCH.ordinal()] = 3; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testCollapsedAndResponseFromCache() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("RollingCollapser-C"); + stream = RollingCollapserEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + for (int i = 0; i < 3; i++) { + CommandStreamTest.Collapser.from(key, i).observe(); + CommandStreamTest.Collapser.from(key, i).observe(); //same arg - should get a response from cache + CommandStreamTest.Collapser.from(key, i).observe(); //same arg - should get a response from cache + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.Collapser.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.Collapser.values().length]; + expected[HystrixEventType.Collapser.BATCH_EXECUTED.ordinal()] = 1; + expected[HystrixEventType.Collapser.ADDED_TO_BATCH.ordinal()] = 3; + expected[HystrixEventType.Collapser.RESPONSE_FROM_CACHE.ordinal()] = 6; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + //by doing a take(30), we expect all values to return to 0 as they age out of rolling window + @Test + public void testCollapsedAndResponseFromCacheAgeOutOfRollingWindow() { + HystrixCollapserKey key = HystrixCollapserKey.Factory.asKey("RollingCollapser-D"); + stream = RollingCollapserEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(getSubscriber(latch)); + + for (int i = 0; i < 3; i++) { + CommandStreamTest.Collapser.from(key, i).observe(); + CommandStreamTest.Collapser.from(key, i).observe(); //same arg - should get a response from cache + CommandStreamTest.Collapser.from(key, i).observe(); //same arg - should get a response from cache + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.Collapser.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.Collapser.values().length]; + expected[HystrixEventType.Collapser.BATCH_EXECUTED.ordinal()] = 0; + expected[HystrixEventType.Collapser.ADDED_TO_BATCH.ordinal()] = 0; + expected[HystrixEventType.Collapser.RESPONSE_FROM_CACHE.ordinal()] = 0; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandEventCounterStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandEventCounterStreamTest.java new file mode 100644 index 0000000..ff49b73 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandEventCounterStreamTest.java @@ -0,0 +1,546 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RollingCommandEventCounterStreamTest extends CommandStreamTest { + HystrixRequestContext context; + RollingCommandEventCounterStream stream; + + static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("RollingCommandCounter"); + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(long[] eventCounts) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : " + bucketToString(eventCounts)); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + RollingCommandEventCounterStream.reset(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-A"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + assertFalse(hasData(stream.getLatest())); + } + + @Test + public void testSingleSuccess() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-B"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 1; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testSingleFailure() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-C"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 1; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testSingleTimeout() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-D"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.TIMEOUT); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.TIMEOUT.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 1; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testSingleBadRequest() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-E"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.BAD_REQUEST); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.BAD_REQUEST.ordinal()] = 1; + expected[HystrixEventType.EXCEPTION_THROWN.ordinal()] = 1; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testRequestFromCache() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-F"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + CommandStreamTest.Command cmd2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + CommandStreamTest.Command cmd3 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + + cmd1.observe(); + cmd2.observe(); + cmd3.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 1; + expected[HystrixEventType.RESPONSE_FROM_CACHE.ordinal()] = 2; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testShortCircuited() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-G"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //3 failures in a row will trip circuit. let bucket roll once then submit 2 requests. + //should see 3 FAILUREs and 2 SHORT_CIRCUITs and then 5 FALLBACK_SUCCESSes + + CommandStreamTest.Command failure1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + CommandStreamTest.Command failure2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + CommandStreamTest.Command failure3 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + CommandStreamTest.Command shortCircuit1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS); + CommandStreamTest.Command shortCircuit2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS); + + failure1.observe(); + failure2.observe(); + failure3.observe(); + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + shortCircuit1.observe(); + shortCircuit2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(shortCircuit1.isResponseShortCircuited()); + assertTrue(shortCircuit2.isResponseShortCircuited()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 3; + expected[HystrixEventType.SHORT_CIRCUITED.ordinal()] = 2; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 5; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testSemaphoreRejected() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-H"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands will saturate semaphore when called from different threads. + //submit 2 more requests and they should be SEMAPHORE_REJECTED + //should see 10 SUCCESSes, 2 SEMAPHORE_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 200, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + CommandStreamTest.Command rejected1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + CommandStreamTest.Command rejected2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + + for (final CommandStreamTest.Command saturator : saturators) { + new Thread(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + saturator.observe(); + } + })).start(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseSemaphoreRejected()); + assertTrue(rejected2.isResponseSemaphoreRejected()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 10; + expected[HystrixEventType.SEMAPHORE_REJECTED.ordinal()] = 2; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 2; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testThreadPoolRejected() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-I"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands will saturate threadpools when called concurrently. + //submit 2 more requests and they should be THREADPOOL_REJECTED + //should see 10 SUCCESSes, 2 THREADPOOL_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 200)); + } + + CommandStreamTest.Command rejected1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + CommandStreamTest.Command rejected2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + + for (final CommandStreamTest.Command saturator : saturators) { + saturator.observe(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseThreadPoolRejected()); + assertTrue(rejected2.isResponseThreadPoolRejected()); + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 10; + expected[HystrixEventType.THREAD_POOL_REJECTED.ordinal()] = 2; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 2; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testFallbackFailure() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-J"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_FAILURE); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_FAILURE.ordinal()] = 1; + expected[HystrixEventType.EXCEPTION_THROWN.ordinal()] = 1; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testFallbackMissing() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-K"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_MISSING); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 1; + expected[HystrixEventType.FALLBACK_MISSING.ordinal()] = 1; + expected[HystrixEventType.EXCEPTION_THROWN.ordinal()] = 1; + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testFallbackRejection() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-L"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //fallback semaphore size is 5. So let 5 commands saturate that semaphore, then + //let 2 more commands go to fallback. they should get rejected by the fallback-semaphore + + List fallbackSaturators = new ArrayList(); + for (int i = 0; i < 5; i++) { + fallbackSaturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 400)); + } + + CommandStreamTest.Command rejection1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + CommandStreamTest.Command rejection2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + + for (CommandStreamTest.Command saturator: fallbackSaturators) { + saturator.observe(); + } + + try { + Thread.sleep(70); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + rejection1.observe(); + rejection2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.FAILURE.ordinal()] = 7; + expected[HystrixEventType.FALLBACK_SUCCESS.ordinal()] = 5; + expected[HystrixEventType.FALLBACK_REJECTION.ordinal()] = 2; + expected[HystrixEventType.EXCEPTION_THROWN.ordinal()] = 2; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testCollapsed() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("BatchCommand"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + for (int i = 0; i < 3; i++) { + CommandStreamTest.Collapser.from(i).observe(); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + expected[HystrixEventType.SUCCESS.ordinal()] = 1; + expected[HystrixEventType.COLLAPSED.ordinal()] = 3; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } + + @Test + public void testMultipleEventsOverTimeGetStoredAndAgeOut() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-RollingCounter-M"); + stream = RollingCommandEventCounterStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + //by doing a take(30), we ensure that all rolling counts go back to 0 + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + CommandStreamTest.Command cmd2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 10); + + cmd1.observe(); + cmd2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(HystrixEventType.values().length, stream.getLatest().length); + long[] expected = new long[HystrixEventType.values().length]; + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertArrayEquals(expected, stream.getLatest()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandLatencyDistributionStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandLatencyDistributionStreamTest.java new file mode 100644 index 0000000..2a289ce --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandLatencyDistributionStreamTest.java @@ -0,0 +1,545 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.metric.CachedValuesHistogram; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RollingCommandLatencyDistributionStreamTest extends CommandStreamTest { + RollingCommandLatencyDistributionStream stream; + HystrixRequestContext context; + static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("CommandLatency"); + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + stream.unsubscribe(); + context.shutdown(); + RollingCommandLatencyDistributionStream.reset(); + } + + @Test + public void testEmptyStreamProducesEmptyDistributions() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-A"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println("OnNext @ " + System.currentTimeMillis()); + assertEquals(0, distribution.getTotalCount()); + } + }); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(0, stream.getLatest().getTotalCount()); + } + + private void assertBetween(int expectedLow, int expectedHigh, int value) { + assertTrue("value too low (" + value + "), expected low = " + expectedLow, expectedLow <= value); + assertTrue("value too high (" + value + "), expected high = " + expectedHigh, expectedHigh >= value); + } + + @Test + public void testSingleBucketGetsStored() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-B"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Received distribution with count : " + distribution.getTotalCount() + " and mean : " + distribution.getMean()); + if (distribution.getTotalCount() == 1) { + assertBetween(10, 50, (int) distribution.getMean()); + } else if (distribution.getTotalCount() == 2) { + assertBetween(300, 400, (int) distribution.getMean()); + } + } + }); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.TIMEOUT); //latency = 600 + cmd1.observe(); + cmd2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + assertBetween(150, 400, stream.getLatestMean()); + assertBetween(10, 50, stream.getLatestPercentile(0.0)); + assertBetween(300, 800, stream.getLatestPercentile(100.0)); + } + + /* + * The following event types should not have their latency measured: + * THREAD_POOL_REJECTED + * SEMAPHORE_REJECTED + * SHORT_CIRCUITED + * RESPONSE_FROM_CACHE + * + * Newly measured (as of 1.5) + * BAD_REQUEST + * FAILURE + * TIMEOUT + */ + @Test + public void testSingleBucketWithMultipleEventTypes() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-C"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Received distribution with count : " + distribution.getTotalCount() + " and mean : " + distribution.getMean()); + if (distribution.getTotalCount() < 4 && distribution.getTotalCount() > 0) { //buckets before timeout latency registers + assertBetween(10, 50, (int) distribution.getMean()); + } else if (distribution.getTotalCount() == 4){ + assertBetween(150, 250, (int) distribution.getMean()); //now timeout latency of 600ms is there + } + } + }); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.TIMEOUT); //latency = 600 + Command cmd3 = Command.from(groupKey, key, HystrixEventType.FAILURE, 30); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.BAD_REQUEST, 40); + + cmd1.observe(); + cmd2.observe(); + cmd3.observe(); + cmd4.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertBetween(150, 350, stream.getLatestMean()); //now timeout latency of 600ms is there + assertBetween(10, 40, stream.getLatestPercentile(0.0)); + assertBetween(600, 800, stream.getLatestPercentile(100.0)); + } + + @Test + public void testShortCircuitedCommandDoesNotGetLatencyTracked() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-D"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + //3 failures is enough to trigger short-circuit. execute those, then wait for bucket to roll + //next command should be a short-circuit + List commands = new ArrayList(); + for (int i = 0; i < 3; i++) { + commands.add(Command.from(groupKey, key, HystrixEventType.FAILURE, 0)); + } + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Received distribution with count : " + distribution.getTotalCount() + " and mean : " + distribution.getMean()); + assertBetween(0, 30, (int) distribution.getMean()); + } + }); + + for (Command cmd: commands) { + cmd.observe(); + } + + Command shortCircuit = Command.from(groupKey, key, HystrixEventType.SUCCESS); + + try { + Thread.sleep(200); + shortCircuit.observe(); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(3, stream.getLatest().getTotalCount()); + assertBetween(0, 30, stream.getLatestMean()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(shortCircuit.isResponseShortCircuited()); + } + + @Test + public void testThreadPoolRejectedCommandDoesNotGetLatencyTracked() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-E"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + //10 commands with latency should occupy the entire threadpool. execute those, then wait for bucket to roll + //next command should be a thread-pool rejection + List commands = new ArrayList(); + for (int i = 0; i < 10; i++) { + commands.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 200)); + } + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Received distribution with count : " + distribution.getTotalCount() + " and mean : " + distribution.getMean()); +// if (distribution.getTotalCount() > 0) { +// assertBetween(200, 250, (int) distribution.getMean()); +// } + } + }); + + for (Command cmd: commands) { + cmd.observe(); + } + + Command threadPoolRejected = Command.from(groupKey, key, HystrixEventType.SUCCESS); + + try { + Thread.sleep(40); + threadPoolRejected.observe(); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(10, stream.getLatest().getTotalCount()); + assertBetween(200, 250, stream.getLatestMean()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(threadPoolRejected.isResponseThreadPoolRejected()); + } + + @Test + public void testSemaphoreRejectedCommandDoesNotGetLatencyTracked() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-F"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + //10 commands with latency should occupy all semaphores. execute those, then wait for bucket to roll + //next command should be a semaphore rejection + List commands = new ArrayList(); + for (int i = 0; i < 10; i++) { + commands.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 200, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Received distribution with count : " + distribution.getTotalCount() + " and mean : " + distribution.getMean()); + if (distribution.getTotalCount() > 0) { + assertBetween(200, 250, (int) distribution.getMean()); + } + } + }); + + for (final Command cmd: commands) { + //since these are blocking calls on the caller thread, we need a new caller thread for each command to actually get the desired concurrency + new Thread(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + cmd.observe(); + } + })).start(); + } + + Command semaphoreRejected = Command.from(groupKey, key, HystrixEventType.SUCCESS); + + try { + Thread.sleep(40); + semaphoreRejected.observe(); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(10, stream.getLatest().getTotalCount()); + assertBetween(200, 250, stream.getLatestMean()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(semaphoreRejected.isResponseSemaphoreRejected()); + } + + @Test + public void testResponseFromCacheDoesNotGetLatencyTracked() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-G"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + //should get 1 SUCCESS and 1 RESPONSE_FROM_CACHE + List commands = Command.getCommandsWithResponseFromCache(groupKey, key); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Received distribution with count : " + distribution.getTotalCount() + " and mean : " + distribution.getMean()); + assertTrue(distribution.getTotalCount() <= 1); + } + }); + + for (Command cmd: commands) { + cmd.observe(); + } + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(1, stream.getLatest().getTotalCount()); + assertBetween(0, 30, stream.getLatestMean()); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + } + + @Test + public void testMultipleBucketsBothGetStored() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-H"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Received distribution with count : " + distribution.getTotalCount() + " and mean : " + distribution.getMean()); + if (distribution.getTotalCount() == 2) { + assertBetween(55, 90, (int) distribution.getMean()); + } + if (distribution.getTotalCount() == 5) { + assertEquals(60, 90, (long) distribution.getMean()); + } + } + }); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.FAILURE, 100); + + cmd1.observe(); + cmd2.observe(); + + try { + Thread.sleep(500); + } catch (InterruptedException ie) { + fail("Interrupted ex"); + } + + Command cmd3 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 60); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 60); + Command cmd5 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 70); + + cmd3.observe(); + cmd4.observe(); + cmd5.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + assertBetween(55, 90, stream.getLatestMean()); + assertBetween(10, 50, stream.getLatestPercentile(0.0)); + assertBetween(100, 150, stream.getLatestPercentile(100.0)); + } + + /** + * The extra takes on the stream should give enough time for all of the measured latencies to age out + */ + @Test + public void testMultipleBucketsBothGetStoredAndThenAgeOut() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Latency-I"); + stream = RollingCommandLatencyDistributionStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(CachedValuesHistogram distribution) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Received distribution with count : " + distribution.getTotalCount() + " and mean : " + distribution.getMean()); + if (distribution.getTotalCount() == 2) { + assertBetween(55, 90, (int) distribution.getMean()); + } + if (distribution.getTotalCount() == 5) { + assertEquals(60, 90, (long) distribution.getMean()); + } + } + }); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.FAILURE, 100); + + cmd1.observe(); + cmd2.observe(); + + try { + Thread.sleep(500); + } catch (InterruptedException ie) { + fail("Interrupted ex"); + } + + Command cmd3 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 60); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 60); + Command cmd5 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 70); + + cmd3.observe(); + cmd4.observe(); + cmd5.observe(); + + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + assertEquals(0, stream.getLatest().getTotalCount()); + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandMaxConcurrencyStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandMaxConcurrencyStreamTest.java new file mode 100644 index 0000000..5947272 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingCommandMaxConcurrencyStreamTest.java @@ -0,0 +1,366 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RollingCommandMaxConcurrencyStreamTest extends CommandStreamTest { + RollingCommandMaxConcurrencyStream stream; + HystrixRequestContext context; + ExecutorService threadPool; + + static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Command-Concurrency"); + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(Integer maxConcurrency) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : Max of " + maxConcurrency); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + threadPool = Executors.newFixedThreadPool(20); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + RollingCommandMaxConcurrencyStream.reset(); + threadPool.shutdown(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-A"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(0, stream.getLatestRollingMax()); + } + + @Test + public void testStartsAndEndsInSameBucketProduceValue() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-B"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 100); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 100); + + cmd1.observe(); + Thread.sleep(1); + cmd2.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(2, stream.getLatestRollingMax()); + } + + /*** + * 3 Commands, + * Command 1 gets started in Bucket A and not completed until Bucket B + * Commands 2 and 3 both start and end in Bucket B, and there should be a max-concurrency of 3 + */ + @Test + public void testOneCommandCarriesOverToNextBucket() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-C"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 160); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 15); + + cmd1.observe(); + Thread.sleep(100); //bucket roll + cmd2.observe(); + Thread.sleep(1); + cmd3.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(3, stream.getLatestRollingMax()); + } + + /** + * BUCKETS + * A | B | C | D | E | + * 1: [-------------------------------] + * 2: [-------------------------------] + * 3: [--] + * 4: [--] + * + * Max concurrency should be 3 + */ + @Test + public void testMultipleCommandsCarryOverMultipleBuckets() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-D"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 300); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 300); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + + cmd1.observe(); + Thread.sleep(100); //bucket roll + cmd2.observe(); + Thread.sleep(100); + cmd3.observe(); + Thread.sleep(100); + cmd4.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(3, stream.getLatestRollingMax()); + } + + /** + * BUCKETS + * A | B | C | D | E | + * 1: [-------------------------------] + * 2: [-------------------------------] + * 3: [--] + * 4: [--] + * + * Max concurrency should be 3, but by waiting for 30 bucket rolls, final max concurrency should be 0 + */ + @Test + public void testMultipleCommandsCarryOverMultipleBucketsAndThenAgeOut() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-E"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 300); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 300); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + + cmd1.observe(); + Thread.sleep(100); //bucket roll + cmd2.observe(); + Thread.sleep(100); + cmd3.observe(); + Thread.sleep(100); + cmd4.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(0, stream.getLatestRollingMax()); + } + + @Test + public void testConcurrencyStreamProperlyFiltersOutResponseFromCache() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-F"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 40); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + + cmd1.observe(); + Thread.sleep(5); + cmd2.observe(); + cmd3.observe(); + cmd4.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, stream.getLatestRollingMax()); + } + + @Test + public void testConcurrencyStreamProperlyFiltersOutShortCircuits() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-G"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //after 3 failures, next command should short-circuit. + //to prove short-circuited commands don't contribute to concurrency, execute 3 FAILURES in the first bucket sequentially + //then when circuit is open, execute 20 concurrent commands. they should all get short-circuited, and max concurrency should be 1 + Command failure1 = Command.from(groupKey, key, HystrixEventType.FAILURE); + Command failure2 = Command.from(groupKey, key, HystrixEventType.FAILURE); + Command failure3 = Command.from(groupKey, key, HystrixEventType.FAILURE); + + List shortCircuited = new ArrayList(); + + for (int i = 0; i < 20; i++) { + shortCircuited.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 100)); + } + + failure1.execute(); + failure2.execute(); + failure3.execute(); + + Thread.sleep(150); + + for (Command cmd: shortCircuited) { + cmd.observe(); + } + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(1, stream.getLatestRollingMax()); + } + + @Test + public void testConcurrencyStreamProperlyFiltersOutSemaphoreRejections() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-H"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands executed concurrently on different caller threads should saturate semaphore + //once these are in-flight, execute 10 more concurrently on new caller threads. + //since these are semaphore-rejected, the max concurrency should be 10 + + List saturators = new ArrayList(); + for (int i = 0; i < 10; i++) { + saturators.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 400, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + final List rejected = new ArrayList(); + for (int i = 0; i < 10; i++) { + rejected.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 100, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + for (final Command saturatingCmd: saturators) { + threadPool.submit(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + saturatingCmd.observe(); + } + })); + } + + Thread.sleep(30); + + for (final Command rejectedCmd: rejected) { + threadPool.submit(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + rejectedCmd.observe(); + } + })); + } + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(10, stream.getLatestRollingMax()); + } + + @Test + public void testConcurrencyStreamProperlyFiltersOutThreadPoolRejections() throws InterruptedException { + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("CMD-Concurrency-I"); + stream = RollingCommandMaxConcurrencyStream.getInstance(key, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands executed concurrently should saturate the Hystrix threadpool + //once these are in-flight, execute 10 more concurrently + //since these are threadpool-rejected, the max concurrency should be 10 + + List saturators = new ArrayList(); + for (int i = 0; i < 10; i++) { + saturators.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 400)); + } + + final List rejected = new ArrayList(); + for (int i = 0; i < 10; i++) { + rejected.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 100)); + } + + for (final Command saturatingCmd: saturators) { + saturatingCmd.observe(); + } + + Thread.sleep(30); + + for (final Command rejectedCmd: rejected) { + rejectedCmd.observe(); + } + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(10, stream.getLatestRollingMax()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolEventCounterStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolEventCounterStreamTest.java new file mode 100644 index 0000000..062aee5 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolEventCounterStreamTest.java @@ -0,0 +1,518 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RollingThreadPoolEventCounterStreamTest extends CommandStreamTest { + HystrixRequestContext context; + RollingThreadPoolEventCounterStream stream; + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(long[] eventCounts) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : " + eventCounts[0] + " : " + eventCounts[1]); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + RollingCommandEventCounterStream.reset(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-A"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-A"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-A"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED) + stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSingleSuccess() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-B"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-B"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-B"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSingleFailure() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-C"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-C"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-C"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSingleTimeout() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-D"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-D"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-D"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.TIMEOUT); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSingleBadRequest() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-E"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-E"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-E"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.BAD_REQUEST); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testRequestFromCache() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-F"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-F"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-F"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + CommandStreamTest.Command cmd2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + CommandStreamTest.Command cmd3 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + + cmd1.observe(); + cmd2.observe(); + cmd3.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + + //RESPONSE_FROM_CACHE should not show up at all in thread pool counters - just the success + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testShortCircuited() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-G"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-G"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-G"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //3 failures in a row will trip circuit. let bucket roll once then submit 2 requests. + //should see 3 FAILUREs and 2 SHORT_CIRCUITs and each should see a FALLBACK_SUCCESS + + CommandStreamTest.Command failure1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + CommandStreamTest.Command failure2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + CommandStreamTest.Command failure3 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20); + + CommandStreamTest.Command shortCircuit1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS); + CommandStreamTest.Command shortCircuit2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS); + + failure1.observe(); + failure2.observe(); + failure3.observe(); + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + shortCircuit1.observe(); + shortCircuit2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(shortCircuit1.isResponseShortCircuited()); + assertTrue(shortCircuit2.isResponseShortCircuited()); + + //only the FAILUREs should show up in thread pool counters + assertEquals(2, stream.getLatest().length); + assertEquals(3, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testSemaphoreRejected() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-H"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-H"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-H"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //10 commands will saturate semaphore when called from different threads. + //submit 2 more requests and they should be SEMAPHORE_REJECTED + //should see 10 SUCCESSes, 2 SEMAPHORE_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 500, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + CommandStreamTest.Command rejected1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + CommandStreamTest.Command rejected2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + + for (final CommandStreamTest.Command saturator : saturators) { + new Thread(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + saturator.observe(); + } + })).start(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseSemaphoreRejected()); + assertTrue(rejected2.isResponseSemaphoreRejected()); + + //none of these got executed on a thread-pool, so thread pool metrics should be 0 + assertEquals(2, stream.getLatest().length); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testThreadPoolRejected() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-I"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-I"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-I"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //10 commands will saturate threadpools when called concurrently. + //submit 2 more requests and they should be THREADPOOL_REJECTED + //should see 10 SUCCESSes, 2 THREADPOOL_REJECTED and 2 FALLBACK_SUCCESSes + + List saturators = new ArrayList(); + + for (int i = 0; i < 10; i++) { + saturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 200)); + } + + CommandStreamTest.Command rejected1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + CommandStreamTest.Command rejected2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 0); + + for (final CommandStreamTest.Command saturator : saturators) { + saturator.observe(); + } + + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + fail(ie.getMessage()); + } + + rejected1.observe(); + rejected2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(rejected1.isResponseThreadPoolRejected()); + assertTrue(rejected2.isResponseThreadPoolRejected()); + + //all 12 commands got submitted to thread pool, 10 accepted, 2 rejected is expected + assertEquals(2, stream.getLatest().length); + assertEquals(10, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(2, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testFallbackFailure() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-J"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-J"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-J"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_FAILURE); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testFallbackMissing() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-K"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-K"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-K"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_MISSING); + + cmd.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(2, stream.getLatest().length); + assertEquals(1, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testFallbackRejection() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-L"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-L"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-L"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 500); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(5).subscribe(getSubscriber(latch)); + + //fallback semaphore size is 5. So let 5 commands saturate that semaphore, then + //let 2 more commands go to fallback. they should get rejected by the fallback-semaphore + + List fallbackSaturators = new ArrayList(); + for (int i = 0; i < 5; i++) { + fallbackSaturators.add(CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 400)); + } + + CommandStreamTest.Command rejection1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + CommandStreamTest.Command rejection2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 20, HystrixEventType.FALLBACK_SUCCESS, 0); + + for (CommandStreamTest.Command saturator: fallbackSaturators) { + saturator.observe(); + } + + try { + Thread.sleep(70); + } catch (InterruptedException ex) { + fail(ex.getMessage()); + } + + rejection1.observe(); + rejection2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + //all 7 commands executed on-thread, so should be executed according to thread-pool metrics + assertEquals(2, stream.getLatest().length); + assertEquals(7, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } + + @Test + public void testMultipleEventsOverTimeGetStoredAndAgeOut() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-M"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-M"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingCounter-M"); + stream = RollingThreadPoolEventCounterStream.getInstance(threadPoolKey, 10, 250); + stream.startCachingStreamValuesIfUnstarted(); + + //by doing a take(20), we ensure that all rolling counts go back to 0 + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(20).subscribe(getSubscriber(latch)); + + CommandStreamTest.Command cmd1 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.SUCCESS, 20); + CommandStreamTest.Command cmd2 = CommandStreamTest.Command.from(groupKey, key, HystrixEventType.FAILURE, 10); + + cmd1.observe(); + cmd2.observe(); + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + + //all commands should have aged out + assertEquals(2, stream.getLatest().length); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.EXECUTED)); + assertEquals(0, stream.getLatestCount(HystrixEventType.ThreadPool.REJECTED)); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolMaxConcurrencyStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolMaxConcurrencyStreamTest.java new file mode 100644 index 0000000..93d5346 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/consumer/RollingThreadPoolMaxConcurrencyStreamTest.java @@ -0,0 +1,464 @@ +/** + * Copyright 2016 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.metric.consumer; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.metric.CommandStreamTest; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import rx.Subscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RollingThreadPoolMaxConcurrencyStreamTest extends CommandStreamTest { + RollingThreadPoolMaxConcurrencyStream stream; + HystrixRequestContext context; + ExecutorService threadPool; + + private static Subscriber getSubscriber(final CountDownLatch latch) { + return new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(Integer maxConcurrency) { + System.out.println("OnNext @ " + System.currentTimeMillis() + " : Max of " + maxConcurrency); + } + }; + } + + @Before + public void setUp() { + context = HystrixRequestContext.initializeContext(); + threadPool = Executors.newFixedThreadPool(20); + } + + @After + public void tearDown() { + context.shutdown(); + stream.unsubscribe(); + RollingCommandMaxConcurrencyStream.reset(); + threadPool.shutdown(); + } + + @Test + public void testEmptyStreamProducesZeros() { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-A"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-A"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-A"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //no writes + + try { + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + fail("Interrupted ex"); + } + assertEquals(0, stream.getLatestRollingMax()); + } + + @Test + public void testStartsAndEndsInSameBucketProduceValue() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-B"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-B"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-B"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 50); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 40); + + cmd1.observe(); + Thread.sleep(1); + cmd2.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(2, stream.getLatestRollingMax()); + } + + @Test + public void testStartsAndEndsInSameBucketSemaphoreIsolated() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-C"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-C"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-C"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 14, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); + + cmd1.observe(); + Thread.sleep(1); + cmd2.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + //since commands run in semaphore isolation, they are not tracked by threadpool metrics + assertEquals(0, stream.getLatestRollingMax()); + } + + + /*** + * 3 Commands, + * Command 1 gets started in Bucket A and not completed until Bucket B + * Commands 2 and 3 both start and end in Bucket B, and there should be a max-concurrency of 3 + */ + @Test + public void testOneCommandCarriesOverToNextBucket() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-D"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-D"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-D"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 560); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 50); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 75); + + cmd1.observe(); + Thread.sleep(150); //bucket roll + cmd2.observe(); + Thread.sleep(1); + cmd3.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(3, stream.getLatestRollingMax()); + } + + /** + * BUCKETS + * A | B | C | D | E | + * 1: [-------------------------------] + * 2: [-------------------------------] + * 3: [--] + * 4: [--] + * + * Max concurrency should be 3 + */ + @Test + public void testMultipleCommandsCarryOverMultipleBuckets() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-E"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-E"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-E"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 300); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 300); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + + cmd1.observe(); + Thread.sleep(100); //bucket roll + cmd2.observe(); + Thread.sleep(100); + cmd3.observe(); + Thread.sleep(100); + cmd4.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(3, stream.getLatestRollingMax()); + } + + /** + * BUCKETS + * A | B | C | D | E | + * 1: [-------------------------------] ThreadPool x + * 2: [-------------------------------] y + * 3: [--] x + * 4: [--] x + * + * Same input data as above test, just that command 2 runs in a separate threadpool, so concurrency should not get tracked + * Max concurrency should be 2 for x + */ + @Test + public void testMultipleCommandsCarryOverMultipleBucketsForMultipleThreadPools() throws InterruptedException { + HystrixCommandGroupKey groupKeyX = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-X"); + HystrixCommandGroupKey groupKeyY = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-Y"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-X"); + HystrixCommandKey keyX = HystrixCommandKey.Factory.asKey("RollingConcurrency-X"); + HystrixCommandKey keyY = HystrixCommandKey.Factory.asKey("RollingConcurrency-Y"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKeyX, keyX, HystrixEventType.SUCCESS, 300); + Command cmd2 = Command.from(groupKeyY, keyY, HystrixEventType.SUCCESS, 300); + Command cmd3 = Command.from(groupKeyX, keyY, HystrixEventType.SUCCESS, 10); + Command cmd4 = Command.from(groupKeyX, keyY, HystrixEventType.SUCCESS, 10); + + cmd1.observe(); + Thread.sleep(100); //bucket roll + cmd2.observe(); + Thread.sleep(100); + cmd3.observe(); + Thread.sleep(100); + cmd4.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(2, stream.getLatestRollingMax()); + } + + /** + * BUCKETS + * A | B | C | D | E | + * 1: [-------------------------------] + * 2: [-------------------------------] + * 3: [--] + * 4: [--] + * + * Max concurrency should be 3, but by waiting for 30 bucket rolls, final max concurrency should be 0 + */ + @Test + public void testMultipleCommandsCarryOverMultipleBucketsAndThenAgeOut() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-F"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-F"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-F"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(30).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 300); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 300); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 10); + + cmd1.observe(); + Thread.sleep(100); //bucket roll + cmd2.observe(); + Thread.sleep(100); + cmd3.observe(); + Thread.sleep(100); + cmd4.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertEquals(0, stream.getLatestRollingMax()); + } + + @Test + public void testConcurrencyStreamProperlyFiltersOutResponseFromCache() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-G"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-G"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-G"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + Command cmd1 = Command.from(groupKey, key, HystrixEventType.SUCCESS, 40); + Command cmd2 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + Command cmd3 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + Command cmd4 = Command.from(groupKey, key, HystrixEventType.RESPONSE_FROM_CACHE); + + cmd1.observe(); + Thread.sleep(5); + cmd2.observe(); + cmd3.observe(); + cmd4.observe(); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertTrue(cmd2.isResponseFromCache()); + assertTrue(cmd3.isResponseFromCache()); + assertTrue(cmd4.isResponseFromCache()); + assertEquals(1, stream.getLatestRollingMax()); + } + + @Test + public void testConcurrencyStreamProperlyFiltersOutShortCircuits() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-H"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-H"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-H"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //after 3 failures, next command should short-circuit. + //to prove short-circuited commands don't contribute to concurrency, execute 3 FAILURES in the first bucket sequentially + //then when circuit is open, execute 20 concurrent commands. they should all get short-circuited, and max concurrency should be 1 + Command failure1 = Command.from(groupKey, key, HystrixEventType.FAILURE); + Command failure2 = Command.from(groupKey, key, HystrixEventType.FAILURE); + Command failure3 = Command.from(groupKey, key, HystrixEventType.FAILURE); + + List shortCircuited = new ArrayList(); + + for (int i = 0; i < 20; i++) { + shortCircuited.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 100)); + } + + failure1.execute(); + failure2.execute(); + failure3.execute(); + + Thread.sleep(150); + + for (Command cmd: shortCircuited) { + cmd.observe(); + } + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + for (Command cmd: shortCircuited) { + assertTrue(cmd.isResponseShortCircuited()); + } + assertEquals(1, stream.getLatestRollingMax()); + } + + @Test + public void testConcurrencyStreamProperlyFiltersOutSemaphoreRejections() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-I"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-I"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-I"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands executed concurrently on different caller threads should saturate semaphore + //once these are in-flight, execute 10 more concurrently on new caller threads. + //since these are semaphore-rejected, the max concurrency should be 10 + + List saturators = new ArrayList(); + for (int i = 0; i < 10; i++) { + saturators.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 400, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + final List rejected = new ArrayList(); + for (int i = 0; i < 10; i++) { + rejected.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 100, HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)); + } + + for (final Command saturatingCmd: saturators) { + threadPool.submit(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + saturatingCmd.observe(); + } + })); + } + + Thread.sleep(30); + + for (final Command rejectedCmd: rejected) { + threadPool.submit(new HystrixContextRunnable(new Runnable() { + @Override + public void run() { + rejectedCmd.observe(); + } + })); + } + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + + for (Command rejectedCmd: rejected) { + assertTrue(rejectedCmd.isResponseSemaphoreRejected() || rejectedCmd.isResponseShortCircuited()); + } + //should be 0 since all are executed in a semaphore + assertEquals(0, stream.getLatestRollingMax()); + } + + @Test + public void testConcurrencyStreamProperlyFiltersOutThreadPoolRejections() throws InterruptedException { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("ThreadPool-Concurrency-J"); + HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool-Concurrency-J"); + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("RollingConcurrency-J"); + stream = RollingThreadPoolMaxConcurrencyStream.getInstance(threadPoolKey, 10, 100); + stream.startCachingStreamValuesIfUnstarted(); + + final CountDownLatch latch = new CountDownLatch(1); + stream.observe().take(10).subscribe(getSubscriber(latch)); + + //10 commands executed concurrently should saturate the Hystrix threadpool + //once these are in-flight, execute 10 more concurrently + //since these are threadpool-rejected, the max concurrency should be 10 + + List saturators = new ArrayList(); + for (int i = 0; i < 10; i++) { + saturators.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 400)); + } + + final List rejected = new ArrayList(); + for (int i = 0; i < 10; i++) { + rejected.add(Command.from(groupKey, key, HystrixEventType.SUCCESS, 100)); + } + + for (final Command saturatingCmd: saturators) { + saturatingCmd.observe(); + } + + Thread.sleep(30); + + for (final Command rejectedCmd: rejected) { + rejectedCmd.observe(); + } + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + for (Command rejectedCmd: rejected) { + assertTrue(rejectedCmd.isResponseThreadPoolRejected()); + } + + //this should not count rejected commands + assertEquals(10, stream.getLatestRollingMax()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/sample/HystrixUtilizationStreamTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/sample/HystrixUtilizationStreamTest.java new file mode 100644 index 0000000..959ac1c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/metric/sample/HystrixUtilizationStreamTest.java @@ -0,0 +1,324 @@ +/** + * Copyright 2016 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.metric.sample; + +import com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.CommandStreamTest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.schedulers.Schedulers; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class HystrixUtilizationStreamTest extends CommandStreamTest { + + @Rule + public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); + + HystrixUtilizationStream stream; + private final static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("Util"); + private final static HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("Command"); + + @Before + public void init() { + stream = HystrixUtilizationStream.getNonSingletonInstanceOnlyUsedInUnitTests(10); + } + + @Test + public void testStreamHasData() throws Exception { + final AtomicBoolean commandShowsUp = new AtomicBoolean(false); + final AtomicBoolean threadPoolShowsUp = new AtomicBoolean(false); + final CountDownLatch latch = new CountDownLatch(1); + final int NUM = 10; + + for (int i = 0; i < 2; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.observe(); + } + + stream.observe().take(NUM).subscribe( + new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnError : " + e); + latch.countDown(); + } + + @Override + public void onNext(HystrixUtilization utilization) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Received data with : " + utilization.getCommandUtilizationMap().size() + " commands"); + if (utilization.getCommandUtilizationMap().containsKey(commandKey)) { + commandShowsUp.set(true); + } + if (!utilization.getThreadPoolUtilizationMap().isEmpty()) { + threadPoolShowsUp.set(true); + } + } + }); + + assertTrue(latch.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(commandShowsUp.get()); + assertTrue(threadPoolShowsUp.get()); + } + + @Test + public void testTwoSubscribersOneUnsubscribes() throws Exception { + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final AtomicInteger payloads1 = new AtomicInteger(0); + final AtomicInteger payloads2 = new AtomicInteger(0); + + Subscription s1 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(HystrixUtilization utilization) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnNext : " + utilization); + payloads1.incrementAndGet(); + } + }); + + Subscription s2 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(HystrixUtilization utilization) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnNext : " + utilization); + payloads2.incrementAndGet(); + } + }); + //execute 1 command, then unsubscribe from first stream. then execute the rest + for (int i = 0; i < 50; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + if (i == 1) { + s1.unsubscribe(); + } + } + + assertTrue(latch1.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("s1 got : " + payloads1.get() + ", s2 got : " + payloads2.get()); + assertTrue("s1 got data", payloads1.get() > 0); + assertTrue("s2 got data", payloads2.get() > 0); + assertTrue("s1 got less data than s2", payloads2.get() > payloads1.get()); + } + + @Test + public void testTwoSubscribersBothUnsubscribe() throws Exception { + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + final AtomicInteger payloads1 = new AtomicInteger(0); + final AtomicInteger payloads2 = new AtomicInteger(0); + + Subscription s1 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch1.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnCompleted"); + latch1.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnError : " + e); + latch1.countDown(); + } + + @Override + public void onNext(HystrixUtilization utilization) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 1 OnNext : " + utilization); + payloads1.incrementAndGet(); + } + }); + + Subscription s2 = stream + .observe() + .take(100) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + latch2.countDown(); + } + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnCompleted"); + latch2.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnError : " + e); + latch2.countDown(); + } + + @Override + public void onNext(HystrixUtilization utilization) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Dashboard 2 OnNext : " + utilization); + payloads2.incrementAndGet(); + } + }); + //execute 2 commands, then unsubscribe from both streams, then execute the rest + for (int i = 0; i < 10; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + if (i == 2) { + s1.unsubscribe(); + s2.unsubscribe(); + } + } + assertFalse(stream.isSourceCurrentlySubscribed()); //both subscriptions have been cancelled - source should be too + + assertTrue(latch1.await(10000, TimeUnit.MILLISECONDS)); + assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS)); + System.out.println("s1 got : " + payloads1.get() + ", s2 got : " + payloads2.get()); + assertTrue("s1 got data", payloads1.get() > 0); + assertTrue("s2 got data", payloads2.get() > 0); + } + + @Test + public void testTwoSubscribersOneSlowOneFast() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean foundError = new AtomicBoolean(false); + + Observable fast = stream + .observe() + .observeOn(Schedulers.newThread()); + Observable slow = stream + .observe() + .observeOn(Schedulers.newThread()) + .map(new Func1() { + @Override + public HystrixUtilization call(HystrixUtilization util) { + try { + Thread.sleep(100); + return util; + } catch (InterruptedException ex) { + return util; + } + } + }); + + Observable checkZippedEqual = Observable.zip(fast, slow, new Func2() { + @Override + public Boolean call(HystrixUtilization payload, HystrixUtilization payload2) { + return payload == payload2; + } + }); + + Subscription s1 = checkZippedEqual + .take(10000) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnCompleted"); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnError : " + e); + e.printStackTrace(); + foundError.set(true); + latch.countDown(); + } + + @Override + public void onNext(Boolean b) { + //System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnNext : " + b); + } + }); + + for (int i = 0; i < 50; i++) { + HystrixCommand cmd = Command.from(groupKey, commandKey, HystrixEventType.SUCCESS, 50); + cmd.execute(); + } + + latch.await(10000, TimeUnit.MILLISECONDS); + assertFalse(foundError.get()); + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/HystrixPluginsTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/HystrixPluginsTest.java new file mode 100644 index 0000000..5e2774c --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/HystrixPluginsTest.java @@ -0,0 +1,488 @@ +/** + * Copyright 2016 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.strategy; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.ServiceConfigurationError; +import java.util.Vector; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.junit.After; +import org.junit.Test; +import org.slf4j.Logger; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.strategy.HystrixPlugins.LoggerSupplier; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; +import com.netflix.hystrix.strategy.properties.HystrixDynamicProperties; +import com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesSystemProperties; +import com.netflix.hystrix.strategy.properties.HystrixDynamicProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; + +public class HystrixPluginsTest { + @After + public void reset() { + //HystrixPlugins.reset(); + dynamicPropertyEvents.clear(); + System.clearProperty("hystrix.plugin.HystrixDynamicProperties.implementation"); + } + + private static ConcurrentLinkedQueue dynamicPropertyEvents = new ConcurrentLinkedQueue(); + + + @Test + public void testDynamicProperties() throws Exception { + fakeServiceLoaderResource = + "FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties"; + HystrixPlugins plugins = setupMockServiceLoader(); + HystrixDynamicProperties properties = plugins.getDynamicProperties(); + plugins.getCommandExecutionHook(); + plugins.getPropertiesStrategy(); + assertTrue(properties instanceof MockHystrixDynamicPropertiesTest); + + assertEvents( + "[serviceloader: META-INF/services/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties" + + ", debug: [Created HystrixDynamicProperties instance by loading from ServiceLoader. Using class: {}, com.netflix.hystrix.strategy.HystrixPluginsTest.MockHystrixDynamicPropertiesTest]" + + ", property: hystrix.plugin.HystrixCommandExecutionHook.implementation" + + ", serviceloader: META-INF/services/com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook" + + ", property: hystrix.plugin.HystrixPropertiesStrategy.implementation" + + ", serviceloader: META-INF/services/com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy]"); + } + + void assertEvents(String expect) throws Exception { + List keys = getEvents(); + String actual = keys.toString(); + if (! actual.equals(expect)) { + javaPrintList(System.out, keys); + } + assertEquals(expect, actual); + } + + static List getEvents() { + return new ArrayList(dynamicPropertyEvents); + } + + static void javaPrintList(Appendable a, Iterable list) throws IOException { + boolean first = true; + + for (String o : list) { + if (first) { + a.append("\"["); + first = false; + } + else { + a.append("\""); + a.append("\n+ \", "); + } + a.append(o); + } + a.append("]\""); + } + + @Test(expected=ServiceConfigurationError.class) + public void testDynamicPropertiesFailure() throws Exception { + /* + * James Bond: Do you expect me to talk? + * Auric Goldfinger: No, Mr. Bond, I expect you to die! + */ + fakeServiceLoaderResource = + "FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesFail"; + HystrixPlugins plugins = setupMockServiceLoader(); + plugins.getDynamicProperties(); + + } + + @Test + public void testDynamicSystemProperties() throws Exception { + //On the off chance this is the first test lets not screw up all the other tests + HystrixPlugins.getInstance(); + + System.setProperty("hystrix.plugin.HystrixDynamicProperties.implementation", + "com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesSystemProperties"); + + HystrixPlugins plugins = setupMockServiceLoader(); + assertTrue(plugins.getDynamicProperties() instanceof HystrixDynamicPropertiesSystemProperties); + + HystrixDynamicProperties p = plugins.getDynamicProperties(); + //Some minimum testing of system properties wrapper + //this probably should be in its own test class. + assertTrue(p.getBoolean("USE_DEFAULT", true).get()); + assertEquals("string", p.getString("USE_DEFAULT", "string").get()); + assertEquals(1L, p.getLong("USE_DEFAULT", 1L).get().longValue()); + assertEquals(1, p.getInteger("USE_DEFAULT", 1).get().intValue()); + assertNotNull(p.getString("path.separator", null).get()); + + assertEvents("[debug: [Created HystrixDynamicProperties instance from System property named \"hystrix.plugin.HystrixDynamicProperties.implementation\". Using class: {}, com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesSystemProperties]]"); + + System.clearProperty("hystrix.plugin.HystrixDynamicProperties.implementation"); + + } + + static String fakeServiceLoaderResource = + "FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties"; + + private HystrixPlugins setupMockServiceLoader() throws Exception { + final ClassLoader realLoader = HystrixPlugins.class.getClassLoader(); + ClassLoader loader = new WrappedClassLoader(realLoader) { + + @Override + public Enumeration getResources(String name) throws IOException { + dynamicPropertyEvents.add("serviceloader: " + name); + final Enumeration r; + if (name.endsWith("META-INF/services/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties")) { + Vector vs = new Vector(); + URL u = super.getResource(fakeServiceLoaderResource); + vs.add(u); + return vs.elements(); + } else { + r = super.getResources(name); + } + return r; + } + }; + final Logger mockLogger = (Logger) + Proxy.newProxyInstance(realLoader, new Class[] {Logger.class}, new MockLoggerInvocationHandler()); + return HystrixPlugins.create(loader, new LoggerSupplier() { + @Override + public Logger getLogger() { + return mockLogger; + } + }); + } + + static class MockLoggerInvocationHandler implements InvocationHandler { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + dynamicPropertyEvents.offer(method.getName() + ": " + asList(args)); + return null; + } + + } + + static class WrappedClassLoader extends ClassLoader { + + final ClassLoader delegate; + + public WrappedClassLoader(ClassLoader delegate) { + super(); + this.delegate = delegate; + } + + public Class loadClass(String name) throws ClassNotFoundException { + return delegate.loadClass(name); + } + + public URL getResource(String name) { + return delegate.getResource(name); + } + + public Enumeration getResources(String name) throws IOException { + return delegate.getResources(name); + } + + public InputStream getResourceAsStream(String name) { + return delegate.getResourceAsStream(name); + } + + public void setDefaultAssertionStatus(boolean enabled) { + delegate.setDefaultAssertionStatus(enabled); + } + + public void setPackageAssertionStatus(String packageName, boolean enabled) { + delegate.setPackageAssertionStatus(packageName, enabled); + } + + public void setClassAssertionStatus(String className, boolean enabled) { + delegate.setClassAssertionStatus(className, enabled); + } + + public void clearAssertionStatus() { + delegate.clearAssertionStatus(); + } + } + + private static class NoOpProperty implements HystrixDynamicProperty { + + @Override + public T get() { + return null; + } + + @Override + public void addCallback(Runnable callback) { + } + + @Override + public String getName() { + return "NOP"; + } + + } + + public static class MockHystrixDynamicPropertiesTest implements HystrixDynamicProperties { + + @Override + public HystrixDynamicProperty getString(String name, String fallback) { + dynamicPropertyEvents.offer("property: " + name); + return new NoOpProperty(); + } + + @Override + public HystrixDynamicProperty getInteger(String name, Integer fallback) { + dynamicPropertyEvents.offer("property: " + name); + return new NoOpProperty(); + } + + @Override + public HystrixDynamicProperty getLong(String name, Long fallback) { + dynamicPropertyEvents.offer("property: " + name); + return new NoOpProperty(); + + } + + @Override + public HystrixDynamicProperty getBoolean(String name, Boolean fallback) { + dynamicPropertyEvents.offer("property: " + name); + return new NoOpProperty(); + + } + + } + + /* @Test + public void testCommandExecutionHookDefaultImpl() { + HystrixCommandExecutionHook impl = HystrixPlugins.getInstance().getCommandExecutionHook(); + assertTrue(impl instanceof HystrixCommandExecutionHookDefault); + } + + @Test + public void testCommandExecutionHookViaRegisterMethod() { + HystrixPlugins.getInstance().registerCommandExecutionHook(new HystrixCommandExecutionHookTestImpl()); + HystrixCommandExecutionHook impl = HystrixPlugins.getInstance().getCommandExecutionHook(); + assertTrue(impl instanceof HystrixCommandExecutionHookTestImpl); + }*/ + + public static class HystrixCommandExecutionHookTestImpl extends HystrixCommandExecutionHook { + } + + /*@Test + public void testEventNotifierDefaultImpl() { + HystrixEventNotifier impl = HystrixPlugins.getInstance().getEventNotifier(); + assertTrue(impl instanceof HystrixEventNotifierDefault); + } + + @Test + public void testEventNotifierViaRegisterMethod() { + HystrixPlugins.getInstance().registerEventNotifier(new HystrixEventNotifierTestImpl()); + HystrixEventNotifier impl = HystrixPlugins.getInstance().getEventNotifier(); + assertTrue(impl instanceof HystrixEventNotifierTestImpl); + } + + @Test + public void testEventNotifierViaProperty() { + try { + String fullClass = HystrixEventNotifierTestImpl.class.getName(); + System.setProperty("hystrix.plugin.HystrixEventNotifier.implementation", fullClass); + HystrixEventNotifier impl = HystrixPlugins.getInstance().getEventNotifier(); + assertTrue(impl instanceof HystrixEventNotifierTestImpl); + } finally { + System.clearProperty("hystrix.plugin.HystrixEventNotifier.implementation"); + } + }*/ + + // inside UnitTest so it is stripped from Javadocs + public static class HystrixEventNotifierTestImpl extends HystrixEventNotifier { + // just use defaults + } + + /*@Test + public void testConcurrencyStrategyDefaultImpl() { + HystrixConcurrencyStrategy impl = HystrixPlugins.getInstance().getConcurrencyStrategy(); + assertTrue(impl instanceof HystrixConcurrencyStrategyDefault); + } + + @Test + public void testConcurrencyStrategyViaRegisterMethod() { + HystrixPlugins.getInstance().registerConcurrencyStrategy(new HystrixConcurrencyStrategyTestImpl()); + HystrixConcurrencyStrategy impl = HystrixPlugins.getInstance().getConcurrencyStrategy(); + assertTrue(impl instanceof HystrixConcurrencyStrategyTestImpl); + } + + @Test + public void testConcurrencyStrategyViaProperty() { + try { + String fullClass = HystrixConcurrencyStrategyTestImpl.class.getName(); + System.setProperty("hystrix.plugin.HystrixConcurrencyStrategy.implementation", fullClass); + HystrixConcurrencyStrategy impl = HystrixPlugins.getInstance().getConcurrencyStrategy(); + assertTrue(impl instanceof HystrixConcurrencyStrategyTestImpl); + } finally { + System.clearProperty("hystrix.plugin.HystrixConcurrencyStrategy.implementation"); + } + }*/ + + // inside UnitTest so it is stripped from Javadocs + public static class HystrixConcurrencyStrategyTestImpl extends HystrixConcurrencyStrategy { + // just use defaults + } + + /*@Test + public void testMetricsPublisherDefaultImpl() { + HystrixMetricsPublisher impl = HystrixPlugins.getInstance().getMetricsPublisher(); + assertTrue(impl instanceof HystrixMetricsPublisherDefault); + } + + @Test + public void testMetricsPublisherViaRegisterMethod() { + HystrixPlugins.getInstance().registerMetricsPublisher(new HystrixMetricsPublisherTestImpl()); + HystrixMetricsPublisher impl = HystrixPlugins.getInstance().getMetricsPublisher(); + assertTrue(impl instanceof HystrixMetricsPublisherTestImpl); + } + + @Test + public void testMetricsPublisherViaProperty() { + try { + String fullClass = HystrixMetricsPublisherTestImpl.class.getName(); + System.setProperty("hystrix.plugin.HystrixMetricsPublisher.implementation", fullClass); + HystrixMetricsPublisher impl = HystrixPlugins.getInstance().getMetricsPublisher(); + assertTrue(impl instanceof HystrixMetricsPublisherTestImpl); + } finally { + System.clearProperty("hystrix.plugin.HystrixMetricsPublisher.implementation"); + } + }*/ + + // inside UnitTest so it is stripped from Javadocs + public static class HystrixMetricsPublisherTestImpl extends HystrixMetricsPublisher { + // just use defaults + } + + /*@Test + public void testPropertiesStrategyDefaultImpl() { + HystrixPropertiesStrategy impl = HystrixPlugins.getInstance().getPropertiesStrategy(); + assertTrue(impl instanceof HystrixPropertiesStrategyDefault); + } + + @Test + public void testPropertiesStrategyViaRegisterMethod() { + HystrixPlugins.getInstance().registerPropertiesStrategy(new HystrixPropertiesStrategyTestImpl()); + HystrixPropertiesStrategy impl = HystrixPlugins.getInstance().getPropertiesStrategy(); + assertTrue(impl instanceof HystrixPropertiesStrategyTestImpl); + } + + @Test + public void testPropertiesStrategyViaProperty() { + try { + String fullClass = HystrixPropertiesStrategyTestImpl.class.getName(); + System.setProperty("hystrix.plugin.HystrixPropertiesStrategy.implementation", fullClass); + HystrixPropertiesStrategy impl = HystrixPlugins.getInstance().getPropertiesStrategy(); + assertTrue(impl instanceof HystrixPropertiesStrategyTestImpl); + } finally { + System.clearProperty("hystrix.plugin.HystrixPropertiesStrategy.implementation"); + } + }*/ + + // inside UnitTest so it is stripped from Javadocs + public static class HystrixPropertiesStrategyTestImpl extends HystrixPropertiesStrategy { + // just use defaults + } + + /*@Test + public void testRequestContextViaPluginInTimeout() { + HystrixPlugins.getInstance().registerConcurrencyStrategy(new HystrixConcurrencyStrategy() { + @Override + public Callable wrapCallable(final Callable callable) { + return new RequestIdCallable(callable); + } + }); + + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + + testRequestIdThreadLocal.set("foobar"); + final AtomicReference valueInTimeout = new AtomicReference(); + + new DummyCommand().toObservable() + .doOnError(new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("initialized = " + HystrixRequestContext.isCurrentThreadInitialized()); + System.out.println("requestId (timeout) = " + testRequestIdThreadLocal.get()); + valueInTimeout.set(testRequestIdThreadLocal.get()); + } + }) + .materialize() + .toBlocking().single(); + + context.shutdown(); + Hystrix.reset(); + + assertEquals("foobar", valueInTimeout.get()); + }*/ + + private static class RequestIdCallable implements Callable { + private final Callable callable; + private final String requestId; + + public RequestIdCallable(Callable callable) { + this.callable = callable; + this.requestId = testRequestIdThreadLocal.get(); + } + + @Override + public T call() throws Exception { + String original = testRequestIdThreadLocal.get(); + testRequestIdThreadLocal.set(requestId); + try { + return callable.call(); + } finally { + testRequestIdThreadLocal.set(original); + } + } + } + + private static final ThreadLocal testRequestIdThreadLocal = new ThreadLocal(); + + public static class DummyCommand extends HystrixCommand { + + public DummyCommand() { + super(HystrixCommandGroupKey.Factory.asKey("Dummy")); + } + + @Override + protected Void run() throws Exception { + System.out.println("requestId (run) = " + testRequestIdThreadLocal.get()); + Thread.sleep(2000); + return null; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyTest.java new file mode 100644 index 0000000..2fd4d37 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyTest.java @@ -0,0 +1,147 @@ +/** + * 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.strategy.concurrency; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.lang.IllegalStateException; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import rx.functions.Action1; +import rx.functions.Func1; + +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixRequestLog; + +public class HystrixConcurrencyStrategyTest { + @Before + public void prepareForTest() { + /* we must call this to simulate a new request lifecycle running and clearing caches */ + HystrixRequestContext.initializeContext(); + } + + @After + public void cleanup() { + shutdownContextIfExists(); + + // force properties to be clean as well + ConfigurationManager.getConfigInstance().clear(); + } + + /** + * If the RequestContext does not get transferred across threads correctly this blows up. + * No specific assertions are necessary. + */ + @Test + public void testRequestContextPropagatesAcrossObserveOnPool() { + new SimpleCommand().execute(); + new SimpleCommand().observe().map(new Func1() { + + @Override + public String call(String s) { + System.out.println("Map => Commands: " + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands()); + return s; + } + }).toBlocking().forEach(new Action1() { + + @Override + public void call(String s) { + System.out.println("Result [" + s + "] => Commands: " + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands()); + } + }); + } + + private static class SimpleCommand extends HystrixCommand { + + public SimpleCommand() { + super(HystrixCommandGroupKey.Factory.asKey("SimpleCommand")); + } + + @Override + protected String run() throws Exception { + if (HystrixRequestContext.isCurrentThreadInitialized()) { + System.out.println("Executing => Commands: " + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands()); + } + return "Hello"; + } + + } + + @Test + public void testThreadContextOnTimeout() { + final AtomicBoolean isInitialized = new AtomicBoolean(); + new TimeoutCommand().toObservable() + .doOnError(new Action1() { + @Override + public void call(Throwable throwable) { + isInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + } + }) + .materialize() + .toBlocking().single(); + + System.out.println("initialized = " + HystrixRequestContext.isCurrentThreadInitialized()); + System.out.println("initialized inside onError = " + isInitialized.get()); + assertEquals(true, isInitialized.get()); + } + + @Test + public void testNoRequestContextOnSimpleConcurencyStrategyWithoutException() throws Exception { + shutdownContextIfExists(); + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.requestLog.enabled", "false"); + + new SimpleCommand().execute(); + + assertTrue("We are able to run the simple command without a context initialization error.", true); + } + + private void shutdownContextIfExists() { + // instead of storing the reference from initialize we'll just get the current state and shutdown + if (HystrixRequestContext.getContextForCurrentThread() != null) { + // it could have been set NULL by the test + HystrixRequestContext.getContextForCurrentThread().shutdown(); + } + } + private static class DummyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {} + + public static class TimeoutCommand extends HystrixCommand { + static final HystrixCommand.Setter properties = HystrixCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TimeoutTest")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionTimeoutInMilliseconds(50)); + + public TimeoutCommand() { + super(properties); + } + + @Override + protected Void run() throws Exception { + Thread.sleep(500); + return null; + } + } + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixContextSchedulerTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixContextSchedulerTest.java new file mode 100644 index 0000000..a0b8401 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixContextSchedulerTest.java @@ -0,0 +1,69 @@ +/** + * 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.strategy.concurrency; + +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; + +import rx.Scheduler; +import rx.functions.Action0; +import rx.schedulers.Schedulers; + +public class HystrixContextSchedulerTest { + + @Test(timeout = 2500) + public void testUnsubscribeWrappedScheduler() throws InterruptedException { + Scheduler s = Schedulers.newThread(); + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch start = new CountDownLatch(1); + final CountDownLatch end = new CountDownLatch(1); + + HystrixContextScheduler hcs = new HystrixContextScheduler(s); + + Scheduler.Worker w = hcs.createWorker(); + try { + w.schedule(new Action0() { + @Override + public void call() { + start.countDown(); + try { + try { + Thread.sleep(5000); + } catch (InterruptedException ex) { + interrupted.set(true); + } + } finally { + end.countDown(); + } + } + }); + + start.await(); + + w.unsubscribe(); + + end.await(); + + assertTrue(interrupted.get()); + } finally { + w.unsubscribe(); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactoryTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactoryTest.java new file mode 100644 index 0000000..b1a7dde --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactoryTest.java @@ -0,0 +1,157 @@ +/** + * 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.strategy.metrics; + +import static junit.framework.Assert.assertNotSame; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +import com.netflix.hystrix.strategy.HystrixPlugins; +import org.junit.Before; +import org.junit.Test; + +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.HystrixThreadPoolProperties; + +public class HystrixMetricsPublisherFactoryTest { + @Before + public void reset() { + HystrixPlugins.reset(); + } + + /** + * Assert that we only call a publisher once for a given Command or ThreadPool key. + */ + @Test + public void testSingleInitializePerKey() { + final TestHystrixMetricsPublisher publisher = new TestHystrixMetricsPublisher(); + HystrixPlugins.getInstance().registerMetricsPublisher(publisher); + final HystrixMetricsPublisherFactory factory = new HystrixMetricsPublisherFactory(); + ArrayList threads = new ArrayList(); + for (int i = 0; i < 20; i++) { + threads.add(new Thread(new Runnable() { + + @Override + public void run() { + factory.getPublisherForCommand(TestCommandKey.TEST_A, null, null, null, null); + factory.getPublisherForCommand(TestCommandKey.TEST_B, null, null, null, null); + factory.getPublisherForThreadPool(TestThreadPoolKey.TEST_A, null, null); + } + + })); + } + + // start them + for (Thread t : threads) { + t.start(); + } + + // wait for them to finish + for (Thread t : threads) { + try { + t.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // we should see 2 commands and 1 threadPool publisher created + assertEquals(2, publisher.commandCounter.get()); + assertEquals(1, publisher.threadCounter.get()); + } + + @Test + public void testMetricsPublisherReset() { + // precondition: HystrixMetricsPublisherFactory class is not loaded. Calling HystrixPlugins.reset() here should be good enough to run this with other tests. + + // set first custom publisher + HystrixCommandKey key = HystrixCommandKey.Factory.asKey("key"); + HystrixMetricsPublisherCommand firstCommand = new HystrixMetricsPublisherCommandDefault(key, null, null, null, null); + HystrixMetricsPublisher firstPublisher = new CustomPublisher(firstCommand); + HystrixPlugins.getInstance().registerMetricsPublisher(firstPublisher); + + // ensure that first custom publisher is used + HystrixMetricsPublisherCommand cmd = HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(key, null, null, null, null); + assertSame(firstCommand, cmd); + + // reset, then change to second custom publisher + HystrixPlugins.reset(); + HystrixMetricsPublisherCommand secondCommand = new HystrixMetricsPublisherCommandDefault(key, null, null, null, null); + HystrixMetricsPublisher secondPublisher = new CustomPublisher(secondCommand); + HystrixPlugins.getInstance().registerMetricsPublisher(secondPublisher); + + // ensure that second custom publisher is used + cmd = HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(key, null, null, null, null); + assertNotSame(firstCommand, cmd); + assertSame(secondCommand, cmd); + } + + private static class TestHystrixMetricsPublisher extends HystrixMetricsPublisher { + + AtomicInteger commandCounter = new AtomicInteger(); + AtomicInteger threadCounter = new AtomicInteger(); + + @Override + public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandOwner, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + return new HystrixMetricsPublisherCommand() { + @Override + public void initialize() { + commandCounter.incrementAndGet(); + } + }; + } + + @Override + public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { + return new HystrixMetricsPublisherThreadPool() { + @Override + public void initialize() { + threadCounter.incrementAndGet(); + } + }; + } + + } + + private static enum TestCommandKey implements HystrixCommandKey { + TEST_A, TEST_B + } + + private static enum TestThreadPoolKey implements HystrixThreadPoolKey { + TEST_A, TEST_B + } + + static class CustomPublisher extends HystrixMetricsPublisher{ + private HystrixMetricsPublisherCommand commandToReturn; + public CustomPublisher(HystrixMetricsPublisherCommand commandToReturn){ + this.commandToReturn = commandToReturn; + } + + @Override + public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { + return commandToReturn; + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusPropertyTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusPropertyTest.java new file mode 100644 index 0000000..e11ecfb --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusPropertyTest.java @@ -0,0 +1,228 @@ +/** + * 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.strategy.properties; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.After; +import org.junit.Test; + +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.DynamicBooleanProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.DynamicIntegerProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.DynamicStringProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.IntegerProperty; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty.StringProperty; + +public class HystrixPropertiesChainedArchaiusPropertyTest { + @After + public void cleanUp() { + // Tests which use ConfigurationManager.getConfigInstance() will leave the singleton in an initialize state, + // this will leave the singleton in a reasonable state between tests. + ConfigurationManager.getConfigInstance().clear(); + } + + @Test + public void testString() throws Exception { + + DynamicStringProperty pString = new DynamicStringProperty("defaultString", "default-default"); + HystrixPropertiesChainedArchaiusProperty.StringProperty fString = new HystrixPropertiesChainedArchaiusProperty.StringProperty("overrideString", pString); + + assertTrue("default-default".equals(fString.get())); + + ConfigurationManager.getConfigInstance().setProperty("defaultString", "default"); + assertTrue("default".equals(fString.get())); + + ConfigurationManager.getConfigInstance().setProperty("overrideString", "override"); + assertTrue("override".equals(fString.get())); + + ConfigurationManager.getConfigInstance().clearProperty("overrideString"); + assertTrue("default".equals(fString.get())); + + ConfigurationManager.getConfigInstance().clearProperty("defaultString"); + assertTrue("default-default".equals(fString.get())); + } + + @Test + public void testInteger() throws Exception { + + DynamicIntegerProperty pInt = new DynamicIntegerProperty("defaultInt", -1); + HystrixPropertiesChainedArchaiusProperty.IntegerProperty fInt = new HystrixPropertiesChainedArchaiusProperty.IntegerProperty("overrideInt", pInt); + + assertTrue(-1 == fInt.get()); + + ConfigurationManager.getConfigInstance().setProperty("defaultInt", 10); + assertTrue(10 == fInt.get()); + + ConfigurationManager.getConfigInstance().setProperty("overrideInt", 11); + assertTrue(11 == fInt.get()); + + ConfigurationManager.getConfigInstance().clearProperty("overrideInt"); + assertTrue(10 == fInt.get()); + + ConfigurationManager.getConfigInstance().clearProperty("defaultInt"); + assertTrue(-1 == fInt.get()); + } + + @Test + public void testBoolean() throws Exception { + + DynamicBooleanProperty pBoolean = new DynamicBooleanProperty("defaultBoolean", true); + HystrixPropertiesChainedArchaiusProperty.BooleanProperty fBoolean = new HystrixPropertiesChainedArchaiusProperty.BooleanProperty("overrideBoolean", pBoolean); + + System.out.println("pBoolean: " + pBoolean.get()); + System.out.println("fBoolean: " + fBoolean.get()); + + assertTrue(fBoolean.get()); + + ConfigurationManager.getConfigInstance().setProperty("defaultBoolean", Boolean.FALSE); + + System.out.println("pBoolean: " + pBoolean.get()); + System.out.println("fBoolean: " + fBoolean.get()); + + assertFalse(fBoolean.get()); + + ConfigurationManager.getConfigInstance().setProperty("overrideBoolean", Boolean.TRUE); + assertTrue(fBoolean.get()); + + ConfigurationManager.getConfigInstance().clearProperty("overrideBoolean"); + assertFalse(fBoolean.get()); + + ConfigurationManager.getConfigInstance().clearProperty("defaultBoolean"); + assertTrue(fBoolean.get()); + } + + @Test + public void testChainingString() throws Exception { + + DynamicStringProperty node1 = new DynamicStringProperty("node1", "v1"); + StringProperty node2 = new HystrixPropertiesChainedArchaiusProperty.StringProperty("node2", node1); + + HystrixPropertiesChainedArchaiusProperty.StringProperty node3 = new HystrixPropertiesChainedArchaiusProperty.StringProperty("node3", node2); + + assertTrue("" + node3.get(), "v1".equals(node3.get())); + + ConfigurationManager.getConfigInstance().setProperty("node1", "v11"); + assertTrue("v11".equals(node3.get())); + + ConfigurationManager.getConfigInstance().setProperty("node2", "v22"); + assertTrue("v22".equals(node3.get())); + + ConfigurationManager.getConfigInstance().clearProperty("node1"); + assertTrue("v22".equals(node3.get())); + + ConfigurationManager.getConfigInstance().setProperty("node3", "v33"); + assertTrue("v33".equals(node3.get())); + + ConfigurationManager.getConfigInstance().clearProperty("node2"); + assertTrue("v33".equals(node3.get())); + + ConfigurationManager.getConfigInstance().setProperty("node2", "v222"); + assertTrue("v33".equals(node3.get())); + + ConfigurationManager.getConfigInstance().clearProperty("node3"); + assertTrue("v222".equals(node3.get())); + + ConfigurationManager.getConfigInstance().clearProperty("node2"); + assertTrue("v1".equals(node3.get())); + + ConfigurationManager.getConfigInstance().setProperty("node2", "v2222"); + assertTrue("v2222".equals(node3.get())); + } + + @Test + public void testChainingInteger() throws Exception { + + DynamicIntegerProperty node1 = new DynamicIntegerProperty("node1", 1); + IntegerProperty node2 = new HystrixPropertiesChainedArchaiusProperty.IntegerProperty("node2", node1); + + HystrixPropertiesChainedArchaiusProperty.IntegerProperty node3 = new HystrixPropertiesChainedArchaiusProperty.IntegerProperty("node3", node2); + + assertTrue("" + node3.get(), 1 == node3.get()); + + ConfigurationManager.getConfigInstance().setProperty("node1", 11); + assertTrue(11 == node3.get()); + + ConfigurationManager.getConfigInstance().setProperty("node2", 22); + assertTrue(22 == node3.get()); + + ConfigurationManager.getConfigInstance().clearProperty("node1"); + assertTrue(22 == node3.get()); + + ConfigurationManager.getConfigInstance().setProperty("node3", 33); + assertTrue(33 == node3.get()); + + ConfigurationManager.getConfigInstance().clearProperty("node2"); + assertTrue(33 == node3.get()); + + ConfigurationManager.getConfigInstance().setProperty("node2", 222); + assertTrue(33 == node3.get()); + + ConfigurationManager.getConfigInstance().clearProperty("node3"); + assertTrue(222 == node3.get()); + + ConfigurationManager.getConfigInstance().clearProperty("node2"); + assertTrue(1 == node3.get()); + + ConfigurationManager.getConfigInstance().setProperty("node2", 2222); + assertTrue(2222 == node3.get()); + } + + @Test + public void testAddCallback() throws Exception { + + final DynamicStringProperty node1 = new DynamicStringProperty("n1", "n1"); + final HystrixPropertiesChainedArchaiusProperty.StringProperty node2 = new HystrixPropertiesChainedArchaiusProperty.StringProperty("n2", node1); + + final AtomicInteger callbackCount = new AtomicInteger(0); + + node2.addCallback(new Runnable() { + @Override + public void run() { + callbackCount.incrementAndGet(); + } + }); + + assertTrue(0 == callbackCount.get()); + + assertTrue("n1".equals(node2.get())); + assertTrue(0 == callbackCount.get()); + + ConfigurationManager.getConfigInstance().setProperty("n1", "n11"); + assertTrue("n11".equals(node2.get())); + assertTrue(0 == callbackCount.get()); + + ConfigurationManager.getConfigInstance().setProperty("n2", "n22"); + assertTrue("n22".equals(node2.get())); + assertTrue(1 == callbackCount.get()); + + ConfigurationManager.getConfigInstance().clearProperty("n1"); + assertTrue("n22".equals(node2.get())); + assertTrue(1 == callbackCount.get()); + + ConfigurationManager.getConfigInstance().setProperty("n2", "n222"); + assertTrue("n222".equals(node2.get())); + assertTrue(2 == callbackCount.get()); + + ConfigurationManager.getConfigInstance().clearProperty("n2"); + assertTrue("n1".equals(node2.get())); + assertTrue(3 == callbackCount.get()); + } + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertyTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertyTest.java new file mode 100644 index 0000000..e4ea154 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertyTest.java @@ -0,0 +1,91 @@ +/** + * 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.strategy.properties; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.netflix.hystrix.strategy.properties.HystrixProperty.Factory; + +public class HystrixPropertyTest { + + @Test + public void testNested1() { + HystrixProperty a = Factory.asProperty("a"); + assertEquals("a", a.get()); + + HystrixProperty aWithDefault = Factory.asProperty(a, "b"); + assertEquals("a", aWithDefault.get()); + } + + @Test + public void testNested2() { + HystrixProperty nullValue = Factory.nullProperty(); + + HystrixProperty withDefault = Factory.asProperty(nullValue, "b"); + assertEquals("b", withDefault.get()); + } + + @Test + public void testNested3() { + HystrixProperty nullValue = Factory.nullProperty(); + HystrixProperty a = Factory.asProperty(nullValue, "a"); + + HystrixProperty withDefault = Factory.asProperty(a, "b"); + assertEquals("a", withDefault.get()); + } + + @Test + public void testNested4() { + HystrixProperty nullValue = Factory.nullProperty(); + HystrixProperty a = Factory.asProperty(nullValue, null); + + HystrixProperty withDefault = Factory.asProperty(a, "b"); + assertEquals("b", withDefault.get()); + } + + @Test + public void testNested5() { + HystrixProperty nullValue = Factory.nullProperty(); + HystrixProperty a = Factory.asProperty(nullValue, null); + + @SuppressWarnings("unchecked") + HystrixProperty withDefault = Factory.asProperty(a, Factory.asProperty("b")); + assertEquals("b", withDefault.get()); + } + + @Test + public void testSeries1() { + HystrixProperty nullValue = Factory.nullProperty(); + HystrixProperty a = Factory.asProperty(nullValue, null); + + @SuppressWarnings("unchecked") + HystrixProperty withDefault = Factory.asProperty(a, nullValue, nullValue, Factory.asProperty("b")); + assertEquals("b", withDefault.get()); + } + + @Test + public void testSeries2() { + HystrixProperty nullValue = Factory.nullProperty(); + HystrixProperty a = Factory.asProperty(nullValue, null); + + @SuppressWarnings("unchecked") + HystrixProperty withDefault = Factory.asProperty(a, nullValue, Factory.asProperty("b"), nullValue, Factory.asProperty("c")); + assertEquals("b", withDefault.get()); + } + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/ExceptionsTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/ExceptionsTest.java new file mode 100644 index 0000000..6e89144 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/ExceptionsTest.java @@ -0,0 +1,23 @@ +package com.netflix.hystrix.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class ExceptionsTest { + + @Test + public void testCastOfException(){ + Exception exception = new IOException("simulated checked exception message"); + try{ + Exceptions.sneakyThrow(exception); + fail(); + } catch(Exception e){ + assertTrue(e instanceof IOException); + } + } +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingNumberTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingNumberTest.java new file mode 100644 index 0000000..895fc18 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingNumberTest.java @@ -0,0 +1,694 @@ +/** + * 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.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import com.netflix.hystrix.util.HystrixRollingNumber.Time; + +public class HystrixRollingNumberTest { + + @Test + public void testCreatesBuckets() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + // confirm the initial settings + assertEquals(200, counter.timeInMilliseconds); + assertEquals(10, counter.numberOfBuckets); + assertEquals(20, counter.bucketSizeInMillseconds); + + // we start out with 0 buckets in the queue + assertEquals(0, counter.buckets.size()); + + // add a success in each interval which should result in all 10 buckets being created with 1 success in each + for (int i = 0; i < counter.numberOfBuckets; i++) { + counter.increment(HystrixRollingNumberEvent.SUCCESS); + time.increment(counter.bucketSizeInMillseconds); + } + + // confirm we have all 10 buckets + assertEquals(10, counter.buckets.size()); + + // add 1 more and we should still only have 10 buckets since that's the max + counter.increment(HystrixRollingNumberEvent.SUCCESS); + assertEquals(10, counter.buckets.size()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testResetBuckets() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // we start out with 0 buckets in the queue + assertEquals(0, counter.buckets.size()); + + // add 1 + counter.increment(HystrixRollingNumberEvent.SUCCESS); + + // confirm we have 1 bucket + assertEquals(1, counter.buckets.size()); + + // confirm we still have 1 bucket + assertEquals(1, counter.buckets.size()); + + // add 1 + counter.increment(HystrixRollingNumberEvent.SUCCESS); + + // we should now have a single bucket with no values in it instead of 2 or more buckets + assertEquals(1, counter.buckets.size()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testEmptyBucketsFillIn() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // add 1 + counter.increment(HystrixRollingNumberEvent.SUCCESS); + + // we should have 1 bucket + assertEquals(1, counter.buckets.size()); + + // wait past 3 bucket time periods (the 1st bucket then 2 empty ones) + time.increment(counter.bucketSizeInMillseconds * 3); + + // add another + counter.increment(HystrixRollingNumberEvent.SUCCESS); + + // we should have 4 (1 + 2 empty + 1 new one) buckets + assertEquals(4, counter.buckets.size()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testIncrementInSingleBucket() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // increment + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.FAILURE); + counter.increment(HystrixRollingNumberEvent.FAILURE); + counter.increment(HystrixRollingNumberEvent.TIMEOUT); + + // we should have 1 bucket + assertEquals(1, counter.buckets.size()); + + // the count should be 4 + assertEquals(4, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SUCCESS).sum()); + assertEquals(2, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.FAILURE).sum()); + assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.TIMEOUT).sum()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testTimeout() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // increment + counter.increment(HystrixRollingNumberEvent.TIMEOUT); + + // we should have 1 bucket + assertEquals(1, counter.buckets.size()); + + // the count should be 1 + assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.TIMEOUT).sum()); + assertEquals(1, counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT)); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds * 3); + + // incremenet again in latest bucket + counter.increment(HystrixRollingNumberEvent.TIMEOUT); + + // we should have 4 buckets + assertEquals(4, counter.buckets.size()); + + // the counts of the last bucket + assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.TIMEOUT).sum()); + + // the total counts + assertEquals(2, counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testShortCircuited() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // increment + counter.increment(HystrixRollingNumberEvent.SHORT_CIRCUITED); + + // we should have 1 bucket + assertEquals(1, counter.buckets.size()); + + // the count should be 1 + assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SHORT_CIRCUITED).sum()); + assertEquals(1, counter.getRollingSum(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds * 3); + + // incremenet again in latest bucket + counter.increment(HystrixRollingNumberEvent.SHORT_CIRCUITED); + + // we should have 4 buckets + assertEquals(4, counter.buckets.size()); + + // the counts of the last bucket + assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SHORT_CIRCUITED).sum()); + + // the total counts + assertEquals(2, counter.getRollingSum(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testThreadPoolRejection() { + testCounterType(HystrixRollingNumberEvent.THREAD_POOL_REJECTED); + } + + @Test + public void testFallbackSuccess() { + testCounterType(HystrixRollingNumberEvent.FALLBACK_SUCCESS); + } + + @Test + public void testFallbackFailure() { + testCounterType(HystrixRollingNumberEvent.FALLBACK_FAILURE); + } + + @Test + public void testExceptionThrow() { + testCounterType(HystrixRollingNumberEvent.EXCEPTION_THROWN); + } + + private void testCounterType(HystrixRollingNumberEvent type) { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // increment + counter.increment(type); + + // we should have 1 bucket + assertEquals(1, counter.buckets.size()); + + // the count should be 1 + assertEquals(1, counter.buckets.getLast().getAdder(type).sum()); + assertEquals(1, counter.getRollingSum(type)); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds * 3); + + // increment again in latest bucket + counter.increment(type); + + // we should have 4 buckets + assertEquals(4, counter.buckets.size()); + + // the counts of the last bucket + assertEquals(1, counter.buckets.getLast().getAdder(type).sum()); + + // the total counts + assertEquals(2, counter.getRollingSum(type)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testIncrementInMultipleBuckets() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // increment + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.FAILURE); + counter.increment(HystrixRollingNumberEvent.FAILURE); + counter.increment(HystrixRollingNumberEvent.TIMEOUT); + counter.increment(HystrixRollingNumberEvent.TIMEOUT); + counter.increment(HystrixRollingNumberEvent.SHORT_CIRCUITED); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds * 3); + + // increment + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.FAILURE); + counter.increment(HystrixRollingNumberEvent.FAILURE); + counter.increment(HystrixRollingNumberEvent.FAILURE); + counter.increment(HystrixRollingNumberEvent.TIMEOUT); + counter.increment(HystrixRollingNumberEvent.SHORT_CIRCUITED); + + // we should have 4 buckets + assertEquals(4, counter.buckets.size()); + + // the counts of the last bucket + assertEquals(2, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SUCCESS).sum()); + assertEquals(3, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.FAILURE).sum()); + assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.TIMEOUT).sum()); + assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SHORT_CIRCUITED).sum()); + + // the total counts + assertEquals(6, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(5, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); + assertEquals(3, counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(2, counter.getRollingSum(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + + // wait until window passes + time.increment(counter.timeInMilliseconds); + + // increment + counter.increment(HystrixRollingNumberEvent.SUCCESS); + + // the total counts should now include only the last bucket after a reset since the window passed + assertEquals(1, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testCounterRetrievalRefreshesBuckets() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // increment + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.SUCCESS); + counter.increment(HystrixRollingNumberEvent.FAILURE); + counter.increment(HystrixRollingNumberEvent.FAILURE); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds * 3); + + // we should have 1 bucket since nothing has triggered the update of buckets in the elapsed time + assertEquals(1, counter.buckets.size()); + + // the total counts + assertEquals(4, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(2, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); + + // we should have 4 buckets as the counter 'gets' should have triggered the buckets being created to fill in time + assertEquals(4, counter.buckets.size()); + + // wait until window passes + time.increment(counter.timeInMilliseconds); + + // the total counts should all be 0 (and the buckets cleared by the get, not only increment) + assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); + + // increment + counter.increment(HystrixRollingNumberEvent.SUCCESS); + + // the total counts should now include only the last bucket after a reset since the window passed + assertEquals(1, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testUpdateMax1() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // increment + counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 10); + + // we should have 1 bucket + assertEquals(1, counter.buckets.size()); + + // the count should be 10 + assertEquals(10, counter.buckets.getLast().getMaxUpdater(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE).max()); + assertEquals(10, counter.getRollingMaxValue(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE)); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds * 3); + + // increment again in latest bucket + counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 20); + + // we should have 4 buckets + assertEquals(4, counter.buckets.size()); + + // the max + assertEquals(20, counter.buckets.getLast().getMaxUpdater(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE).max()); + + // counts per bucket + long values[] = counter.getValues(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE); + assertEquals(10, values[0]); // oldest bucket + assertEquals(0, values[1]); + assertEquals(0, values[2]); + assertEquals(20, values[3]); // latest bucket + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testUpdateMax2() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + // increment + counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 10); + counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 30); + counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 20); + + // we should have 1 bucket + assertEquals(1, counter.buckets.size()); + + // the count should be 30 + assertEquals(30, counter.buckets.getLast().getMaxUpdater(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE).max()); + assertEquals(30, counter.getRollingMaxValue(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE)); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds * 3); + + counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 30); + counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 30); + counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 50); + + // we should have 4 buckets + assertEquals(4, counter.buckets.size()); + + // the count + assertEquals(50, counter.buckets.getLast().getMaxUpdater(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE).max()); + assertEquals(50, counter.getValueOfLatestBucket(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE)); + + // values per bucket + long values[] = counter.getValues(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE); + assertEquals(30, values[0]); // oldest bucket + assertEquals(0, values[1]); + assertEquals(0, values[2]); + assertEquals(50, values[3]); // latest bucket + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testMaxValue() { + MockedTime time = new MockedTime(); + try { + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.THREAD_MAX_ACTIVE; + + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + + counter.updateRollingMax(type, 10); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds); + + counter.updateRollingMax(type, 30); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds); + + counter.updateRollingMax(type, 40); + + // sleep to get to a new bucket + time.increment(counter.bucketSizeInMillseconds); + + counter.updateRollingMax(type, 15); + + assertEquals(40, counter.getRollingMaxValue(type)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Exception: " + e.getMessage()); + } + } + + @Test + public void testEmptySum() { + MockedTime time = new MockedTime(); + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.COLLAPSED; + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + assertEquals(0, counter.getRollingSum(type)); + } + + @Test + public void testEmptyMax() { + MockedTime time = new MockedTime(); + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.THREAD_MAX_ACTIVE; + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + assertEquals(0, counter.getRollingMaxValue(type)); + } + + @Test + public void testEmptyLatestValue() { + MockedTime time = new MockedTime(); + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.THREAD_MAX_ACTIVE; + HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); + assertEquals(0, counter.getValueOfLatestBucket(type)); + } + + @Test + public void testRolling() { + MockedTime time = new MockedTime(); + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.THREAD_MAX_ACTIVE; + HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); + // iterate over 20 buckets on a queue sized for 2 + for (int i = 0; i < 20; i++) { + // first bucket + counter.getCurrentBucket(); + try { + time.increment(counter.bucketSizeInMillseconds); + } catch (Exception e) { + // ignore + } + + assertEquals(2, counter.getValues(type).length); + + counter.getValueOfLatestBucket(type); + + // System.out.println("Head: " + counter.buckets.state.get().head); + // System.out.println("Tail: " + counter.buckets.state.get().tail); + } + } + + @Test + public void testCumulativeCounterAfterRolling() { + MockedTime time = new MockedTime(); + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.SUCCESS; + HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); + + assertEquals(0, counter.getCumulativeSum(type)); + + // iterate over 20 buckets on a queue sized for 2 + for (int i = 0; i < 20; i++) { + // first bucket + counter.increment(type); + try { + time.increment(counter.bucketSizeInMillseconds); + } catch (Exception e) { + // ignore + } + + assertEquals(2, counter.getValues(type).length); + + counter.getValueOfLatestBucket(type); + + } + + // cumulative count should be 20 (for the number of loops above) regardless of buckets rolling + assertEquals(20, counter.getCumulativeSum(type)); + } + + @Test + public void testCumulativeCounterAfterRollingAndReset() { + MockedTime time = new MockedTime(); + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.SUCCESS; + HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); + + assertEquals(0, counter.getCumulativeSum(type)); + + // iterate over 20 buckets on a queue sized for 2 + for (int i = 0; i < 20; i++) { + // first bucket + counter.increment(type); + try { + time.increment(counter.bucketSizeInMillseconds); + } catch (Exception e) { + // ignore + } + + assertEquals(2, counter.getValues(type).length); + + counter.getValueOfLatestBucket(type); + + if (i == 5 || i == 15) { + // simulate a reset occurring every once in a while + // so we ensure the absolute sum is handling it okay + counter.reset(); + } + } + + // cumulative count should be 20 (for the number of loops above) regardless of buckets rolling + assertEquals(20, counter.getCumulativeSum(type)); + } + + @Test + public void testCumulativeCounterAfterRollingAndReset2() { + MockedTime time = new MockedTime(); + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.SUCCESS; + HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); + + assertEquals(0, counter.getCumulativeSum(type)); + + counter.increment(type); + counter.increment(type); + counter.increment(type); + + // iterate over 20 buckets on a queue sized for 2 + for (int i = 0; i < 20; i++) { + try { + time.increment(counter.bucketSizeInMillseconds); + } catch (Exception e) { + // ignore + } + + if (i == 5 || i == 15) { + // simulate a reset occurring every once in a while + // so we ensure the absolute sum is handling it okay + counter.reset(); + } + } + + // no increments during the loop, just some before and after + counter.increment(type); + counter.increment(type); + + // cumulative count should be 5 regardless of buckets rolling + assertEquals(5, counter.getCumulativeSum(type)); + } + + @Test + public void testCumulativeCounterAfterRollingAndReset3() { + MockedTime time = new MockedTime(); + HystrixRollingNumberEvent type = HystrixRollingNumberEvent.SUCCESS; + HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); + + assertEquals(0, counter.getCumulativeSum(type)); + + counter.increment(type); + counter.increment(type); + counter.increment(type); + + // iterate over 20 buckets on a queue sized for 2 + for (int i = 0; i < 20; i++) { + try { + time.increment(counter.bucketSizeInMillseconds); + } catch (Exception e) { + // ignore + } + } + + // since we are rolling over the buckets it should reset naturally + + // no increments during the loop, just some before and after + counter.increment(type); + counter.increment(type); + + // cumulative count should be 5 regardless of buckets rolling + assertEquals(5, counter.getCumulativeSum(type)); + } + + private static class MockedTime implements Time { + + private AtomicInteger time = new AtomicInteger(0); + + @Override + public long getCurrentTimeInMillis() { + return time.get(); + } + + public void increment(int millis) { + time.addAndGet(millis); + } + + } + +} diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingPercentileTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingPercentileTest.java new file mode 100644 index 0000000..c9f81ec --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingPercentileTest.java @@ -0,0 +1,797 @@ +/** + * 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.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.HystrixRollingPercentile.PercentileSnapshot; +import com.netflix.hystrix.util.HystrixRollingPercentile.Time; + +public class HystrixRollingPercentileTest { + + private static final int timeInMilliseconds = 60000; + private static final int numberOfBuckets = 12; // 12 buckets at 5000ms each + private static final int bucketDataLength = 1000; + private static final HystrixProperty enabled = HystrixProperty.Factory.asProperty(true); + + private static ExecutorService threadPool; + + @BeforeClass + public static void setUp() { + threadPool = Executors.newFixedThreadPool(10); + } + + @AfterClass + public static void tearDown() { + threadPool.shutdown(); + try { + threadPool.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + System.out.println("Thread pool never terminated in HystrixRollingPercentileTest"); + } + } + + @Test + public void testRolling() { + MockedTime time = new MockedTime(); + HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); + p.addValue(1000); + p.addValue(1000); + p.addValue(1000); + p.addValue(2000); + + assertEquals(1, p.buckets.size()); + + // no bucket turnover yet so percentile not yet generated + assertEquals(0, p.getPercentile(50)); + + time.increment(6000); + + // still only 1 bucket until we touch it again + assertEquals(1, p.buckets.size()); + + // a bucket has been created so we have a new percentile + assertEquals(1000, p.getPercentile(50)); + + // now 2 buckets since getting a percentile causes bucket retrieval + assertEquals(2, p.buckets.size()); + + p.addValue(1000); + p.addValue(500); + + // should still be 2 buckets + assertEquals(2, p.buckets.size()); + + p.addValue(200); + p.addValue(200); + p.addValue(1600); + p.addValue(200); + p.addValue(1600); + p.addValue(1600); + + // we haven't progressed to a new bucket so the percentile should be the same and ignore the most recent bucket + assertEquals(1000, p.getPercentile(50)); + + // increment to another bucket so we include all of the above in the PercentileSnapshot + time.increment(6000); + + // the rolling version should have the same data as creating a snapshot like this + PercentileSnapshot ps = new PercentileSnapshot(1000, 1000, 1000, 2000, 1000, 500, 200, 200, 1600, 200, 1600, 1600); + + assertEquals(ps.getPercentile(0.15), p.getPercentile(0.15)); + assertEquals(ps.getPercentile(0.50), p.getPercentile(0.50)); + assertEquals(ps.getPercentile(0.90), p.getPercentile(0.90)); + assertEquals(ps.getPercentile(0.995), p.getPercentile(0.995)); + + System.out.println("100th: " + ps.getPercentile(100) + " " + p.getPercentile(100)); + System.out.println("99.5th: " + ps.getPercentile(99.5) + " " + p.getPercentile(99.5)); + System.out.println("99th: " + ps.getPercentile(99) + " " + p.getPercentile(99)); + System.out.println("90th: " + ps.getPercentile(90) + " " + p.getPercentile(90)); + System.out.println("50th: " + ps.getPercentile(50) + " " + p.getPercentile(50)); + System.out.println("10th: " + ps.getPercentile(10) + " " + p.getPercentile(10)); + + // mean = 1000+1000+1000+2000+1000+500+200+200+1600+200+1600+1600/12 + assertEquals(991, ps.getMean()); + } + + @Test + public void testValueIsZeroAfterRollingWindowPassesAndNoTraffic() { + MockedTime time = new MockedTime(); + HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); + p.addValue(1000); + p.addValue(1000); + p.addValue(1000); + p.addValue(2000); + p.addValue(4000); + + assertEquals(1, p.buckets.size()); + + // no bucket turnover yet so percentile not yet generated + assertEquals(0, p.getPercentile(50)); + + time.increment(6000); + + // still only 1 bucket until we touch it again + assertEquals(1, p.buckets.size()); + + // a bucket has been created so we have a new percentile + assertEquals(1500, p.getPercentile(50)); + + // let 1 minute pass + time.increment(60000); + + // no data in a minute should mean all buckets are empty (or reset) so we should not have any percentiles + assertEquals(0, p.getPercentile(50)); + } + + @Test + public void testSampleDataOverTime1() { + System.out.println("\n\n***************************** testSampleDataOverTime1 \n"); + + MockedTime time = new MockedTime(); + HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); + int previousTime = 0; + for (int i = 0; i < SampleDataHolder1.data.length; i++) { + int timeInMillisecondsSinceStart = SampleDataHolder1.data[i][0]; + int latency = SampleDataHolder1.data[i][1]; + time.increment(timeInMillisecondsSinceStart - previousTime); + previousTime = timeInMillisecondsSinceStart; + p.addValue(latency); + } + + System.out.println("0.01: " + p.getPercentile(0.01)); + System.out.println("Median: " + p.getPercentile(50)); + System.out.println("90th: " + p.getPercentile(90)); + System.out.println("99th: " + p.getPercentile(99)); + System.out.println("99.5th: " + p.getPercentile(99.5)); + System.out.println("99.99: " + p.getPercentile(99.99)); + + System.out.println("Median: " + p.getPercentile(50)); + System.out.println("Median: " + p.getPercentile(50)); + System.out.println("Median: " + p.getPercentile(50)); + + /* + * In a loop as a use case was found where very different values were calculated in subsequent requests. + */ + for (int i = 0; i < 10; i++) { + if (p.getPercentile(50) > 5) { + fail("We expect around 2 but got: " + p.getPercentile(50)); + } + + if (p.getPercentile(99.5) < 20) { + fail("We expect to see some high values over 20 but got: " + p.getPercentile(99.5)); + } + } + } + + @Test + public void testSampleDataOverTime2() { + System.out.println("\n\n***************************** testSampleDataOverTime2 \n"); + MockedTime time = new MockedTime(); + int previousTime = 0; + HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); + for (int i = 0; i < SampleDataHolder2.data.length; i++) { + int timeInMillisecondsSinceStart = SampleDataHolder2.data[i][0]; + int latency = SampleDataHolder2.data[i][1]; + time.increment(timeInMillisecondsSinceStart - previousTime); + previousTime = timeInMillisecondsSinceStart; + p.addValue(latency); + } + + System.out.println("0.01: " + p.getPercentile(0.01)); + System.out.println("Median: " + p.getPercentile(50)); + System.out.println("90th: " + p.getPercentile(90)); + System.out.println("99th: " + p.getPercentile(99)); + System.out.println("99.5th: " + p.getPercentile(99.5)); + System.out.println("99.99: " + p.getPercentile(99.99)); + + if (p.getPercentile(50) > 90 || p.getPercentile(50) < 50) { + fail("We expect around 60-70 but got: " + p.getPercentile(50)); + } + + if (p.getPercentile(99) < 400) { + fail("We expect to see some high values over 400 but got: " + p.getPercentile(99)); + } + } + + public PercentileSnapshot getPercentileForValues(int... values) { + return new PercentileSnapshot(values); + } + + @Test + public void testPercentileAlgorithm_Median1() { + PercentileSnapshot list = new PercentileSnapshot(100, 100, 100, 100, 200, 200, 200, 300, 300, 300, 300); + Assert.assertEquals(200, list.getPercentile(50)); + } + + @Test + public void testPercentileAlgorithm_Median2() { + PercentileSnapshot list = new PercentileSnapshot(100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 500); + Assert.assertEquals(100, list.getPercentile(50)); + } + + @Test + public void testPercentileAlgorithm_Median3() { + PercentileSnapshot list = new PercentileSnapshot(50, 75, 100, 125, 160, 170, 180, 200, 210, 300, 500); + // list.addValue(50); // 1 + // list.addValue(75); // 2 + // list.addValue(100); // 3 + // list.addValue(125); // 4 + // list.addValue(160); // 5 + // list.addValue(170); // 6 + // list.addValue(180); // 7 + // list.addValue(200); // 8 + // list.addValue(210); // 9 + // list.addValue(300); // 10 + // list.addValue(500); // 11 + + Assert.assertEquals(175, list.getPercentile(50)); + } + + @Test + public void testPercentileAlgorithm_Median4() { + PercentileSnapshot list = new PercentileSnapshot(300, 75, 125, 500, 100, 160, 180, 200, 210, 50, 170); + // unsorted so it is expected to sort it for us + // list.addValue(300); // 10 + // list.addValue(75); // 2 + // list.addValue(125); // 4 + // list.addValue(500); // 11 + // list.addValue(100); // 3 + // list.addValue(160); // 5 + // list.addValue(180); // 7 + // list.addValue(200); // 8 + // list.addValue(210); // 9 + // list.addValue(50); // 1 + // list.addValue(170); // 6 + + Assert.assertEquals(175, list.getPercentile(50)); + } + + @Test + public void testPercentileAlgorithm_Extremes() { + PercentileSnapshot p = new PercentileSnapshot(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 800, 768, 657, 700, 867); + + System.out.println("0.01: " + p.getPercentile(0.01)); + System.out.println("10th: " + p.getPercentile(10)); + System.out.println("Median: " + p.getPercentile(50)); + System.out.println("75th: " + p.getPercentile(75)); + System.out.println("90th: " + p.getPercentile(90)); + System.out.println("99th: " + p.getPercentile(99)); + System.out.println("99.5th: " + p.getPercentile(99.5)); + System.out.println("99.99: " + p.getPercentile(99.99)); + Assert.assertEquals(2, p.getPercentile(50)); + Assert.assertEquals(2, p.getPercentile(10)); + Assert.assertEquals(2, p.getPercentile(75)); + if (p.getPercentile(95) < 600) { + fail("We expect the 90th to be over 600 to show the extremes but got: " + p.getPercentile(90)); + } + if (p.getPercentile(99) < 600) { + fail("We expect the 99th to be over 600 to show the extremes but got: " + p.getPercentile(99)); + } + } + + @Test + public void testPercentileAlgorithm_HighPercentile() { + PercentileSnapshot p = getPercentileForValues(1, 2, 3); + Assert.assertEquals(2, p.getPercentile(50)); + Assert.assertEquals(3, p.getPercentile(75)); + } + + @Test + public void testPercentileAlgorithm_LowPercentile() { + PercentileSnapshot p = getPercentileForValues(1, 2); + Assert.assertEquals(1, p.getPercentile(25)); + Assert.assertEquals(2, p.getPercentile(75)); + } + + @Test + public void testPercentileAlgorithm_Percentiles() { + PercentileSnapshot p = getPercentileForValues(10, 30, 20, 40); + Assert.assertEquals(22, p.getPercentile(30), 1.0e-5); + Assert.assertEquals(20, p.getPercentile(25), 1.0e-5); + Assert.assertEquals(40, p.getPercentile(75), 1.0e-5); + Assert.assertEquals(30, p.getPercentile(50), 1.0e-5); + + // invalid percentiles + Assert.assertEquals(10, p.getPercentile(-1)); + Assert.assertEquals(40, p.getPercentile(101)); + } + + @Test + public void testPercentileAlgorithm_NISTExample() { + PercentileSnapshot p = getPercentileForValues(951772, 951567, 951937, 951959, 951442, 950610, 951591, 951195, 951772, 950925, 951990, 951682); + Assert.assertEquals(951983, p.getPercentile(90)); + Assert.assertEquals(951990, p.getPercentile(100)); + } + + /** + * This code should work without throwing exceptions but the data returned will all be -1 since the rolling percentile is disabled. + */ + @Test + public void testDoesNothingWhenDisabled() { + MockedTime time = new MockedTime(); + int previousTime = 0; + HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, HystrixProperty.Factory.asProperty(false)); + for (int i = 0; i < SampleDataHolder2.data.length; i++) { + int timeInMillisecondsSinceStart = SampleDataHolder2.data[i][0]; + int latency = SampleDataHolder2.data[i][1]; + time.increment(timeInMillisecondsSinceStart - previousTime); + previousTime = timeInMillisecondsSinceStart; + p.addValue(latency); + } + + assertEquals(-1, p.getPercentile(50)); + assertEquals(-1, p.getPercentile(75)); + assertEquals(-1, p.getMean()); + } + + @Test + public void testThreadSafety() { + final MockedTime time = new MockedTime(); + final HystrixRollingPercentile p = new HystrixRollingPercentile(time, 100, 25, 1000, HystrixProperty.Factory.asProperty(true)); + + final int NUM_THREADS = 1000; + final int NUM_ITERATIONS = 1000000; + + final CountDownLatch latch = new CountDownLatch(NUM_THREADS); + + final AtomicInteger aggregateMetrics = new AtomicInteger(); //same as a blackhole + + final Random r = new Random(); + + Future metricsPoller = threadPool.submit(new Runnable() { + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + aggregateMetrics.addAndGet(p.getMean() + p.getPercentile(10) + p.getPercentile(50) + p.getPercentile(90)); + //System.out.println("AGGREGATE : " + p.getPercentile(10) + " : " + p.getPercentile(50) + " : " + p.getPercentile(90)); + } + } + }); + + for (int i = 0; i < NUM_THREADS; i++) { + final int threadId = i; + threadPool.submit(new Runnable() { + @Override + public void run() { + for (int j = 1; j < NUM_ITERATIONS / NUM_THREADS + 1; j++) { + int nextInt = r.nextInt(100); + p.addValue(nextInt); + if (threadId == 0) { + time.increment(1); + } + } + latch.countDown(); + } + }); + } + + try { + latch.await(100, TimeUnit.SECONDS); + metricsPoller.cancel(true); + } catch (InterruptedException ex) { + fail("Timeout on all threads writing percentiles"); + } + + aggregateMetrics.addAndGet(p.getMean() + p.getPercentile(10) + p.getPercentile(50) + p.getPercentile(90)); + System.out.println(p.getMean() + " : " + p.getPercentile(50) + " : " + p.getPercentile(75) + " : " + p.getPercentile(90) + " : " + p.getPercentile(95) + " : " + p.getPercentile(99)); + } + + @Test + public void testWriteThreadSafety() { + final MockedTime time = new MockedTime(); + final HystrixRollingPercentile p = new HystrixRollingPercentile(time, 100, 25, 1000, HystrixProperty.Factory.asProperty(true)); + + final int NUM_THREADS = 10; + final int NUM_ITERATIONS = 1000; + + final CountDownLatch latch = new CountDownLatch(NUM_THREADS); + + final Random r = new Random(); + + final AtomicInteger added = new AtomicInteger(0); + + for (int i = 0; i < NUM_THREADS; i++) { + threadPool.submit(new Runnable() { + @Override + public void run() { + for (int j = 1; j < NUM_ITERATIONS / NUM_THREADS + 1; j++) { + int nextInt = r.nextInt(100); + p.addValue(nextInt); + added.getAndIncrement(); + } + latch.countDown(); + } + }); + } + + try { + latch.await(100, TimeUnit.SECONDS); + assertEquals(added.get(), p.buckets.peekLast().data.length()); + } catch (InterruptedException ex) { + fail("Timeout on all threads writing percentiles"); + } + } + + @Test + public void testThreadSafetyMulti() { + for (int i = 0; i < 100; i++) { + testThreadSafety(); + } + } + + + private static class MockedTime implements Time { + + private AtomicInteger time = new AtomicInteger(0); + + @Override + public long getCurrentTimeInMillis() { + return time.get(); + } + + public void increment(int millis) { + time.addAndGet(millis); + } + + } + + /* sub-class to avoid 65k limit of a single class */ + private static class SampleDataHolder1 { + /* + * Array of [milliseconds, latency] + */ + private static int[][] data = new int[][] { + { 0, 3 }, { 43, 33 }, { 45, 11 }, { 45, 1 }, { 68, 13 }, { 88, 10 }, { 158, 2 }, { 158, 4 }, { 169, 12 }, { 267, 2 }, { 342, 2 }, { 438, 2 }, { 464, 7 }, { 504, 2 }, { 541, 6 }, { 541, 2 }, { 562, 2 }, { 581, 3 }, { 636, 2 }, { 778, 2 }, { 825, 1 }, { 859, 2 }, { 948, 1 }, { 1043, 2 }, { 1145, 2 }, { 1152, 1 }, { 1218, 5 }, + { 1229, 2 }, { 1259, 2 }, { 1333, 2 }, { 1349, 2 }, { 1392, 2 }, { 1468, 1 }, { 1551, 2 }, { 1586, 2 }, { 1685, 2 }, { 1696, 1 }, { 1807, 2 }, { 1817, 3 }, { 1817, 6 }, { 1847, 2 }, { 1870, 2 }, { 1939, 2 }, { 2050, 2 }, { 2129, 3 }, { 2141, 2 }, { 2265, 2 }, { 2414, 1 }, { 2693, 2 }, { 2703, 2 }, { 2791, 2 }, { 2838, 2 }, + { 2906, 2 }, { 2981, 2 }, { 3008, 2 }, { 3026, 4 }, { 3077, 2 }, { 3273, 2 }, { 3282, 2 }, { 3286, 2 }, { 3318, 3 }, { 3335, 5 }, { 3710, 2 }, { 3711, 1 }, { 3745, 2 }, { 3748, 4 }, { 3767, 3 }, { 3809, 3 }, { 3835, 35 }, { 4083, 1 }, { 4116, 2 }, { 4117, 1 }, { 4157, 1 }, { 4279, 2 }, { 4344, 2 }, { 4452, 2 }, { 4530, 2 }, + { 4583, 2 }, { 4647, 3 }, { 4758, 2 }, { 4776, 2 }, { 4793, 2 }, { 4901, 2 }, { 4909, 2 }, { 4962, 2 }, { 4984, 2 }, { 5022, 2 }, { 5139, 2 }, { 5166, 1 }, { 5174, 2 }, { 5187, 2 }, { 5225, 2 }, { 5234, 2 }, { 5263, 1 }, { 5325, 2 }, { 5355, 4 }, { 5407, 1 }, { 5414, 2 }, { 5589, 2 }, { 5595, 2 }, { 5747, 2 }, { 5780, 2 }, + { 5788, 2 }, { 5796, 2 }, { 5818, 2 }, { 5975, 1 }, { 6018, 1 }, { 6270, 2 }, { 6272, 2 }, { 6348, 2 }, { 6372, 2 }, { 6379, 2 }, { 6439, 2 }, { 6442, 2 }, { 6460, 2 }, { 6460, 2 }, { 6509, 2 }, { 6511, 1 }, { 6514, 4 }, { 6530, 8 }, { 6719, 2 }, { 6760, 2 }, { 6784, 2 }, { 6838, 1 }, { 6861, 2 }, { 6947, 2 }, { 7013, 2 }, + { 7075, 2 }, { 7122, 5 }, { 7130, 2 }, { 7209, 3 }, { 7259, 2 }, { 7309, 1 }, { 7315, 3 }, { 7322, 2 }, { 7348, 2 }, { 7420, 2 }, { 7461, 2 }, { 7545, 2 }, { 7554, 3 }, { 7630, 2 }, { 7666, 2 }, { 7815, 1 }, { 7972, 1 }, { 7972, 2 }, { 7988, 2 }, { 8049, 8 }, { 8254, 2 }, { 8269, 2 }, { 8352, 1 }, { 8378, 2 }, { 8526, 2 }, + { 8531, 2 }, { 8583, 2 }, { 8615, 2 }, { 8619, 3 }, { 8623, 2 }, { 8692, 1 }, { 8698, 2 }, { 8773, 2 }, { 8777, 3 }, { 8822, 2 }, { 8929, 2 }, { 8935, 2 }, { 9025, 2 }, { 9054, 2 }, { 9056, 1 }, { 9086, 2 }, { 9147, 3 }, { 9219, 2 }, { 9230, 3 }, { 9248, 2 }, { 9283, 2 }, { 9314, 2 }, { 9418, 1 }, { 9426, 2 }, { 9456, 1 }, + { 9594, 2 }, { 9628, 2 }, { 9642, 2 }, { 9646, 2 }, { 9686, 1 }, { 9709, 2 }, { 9771, 3 }, { 9782, 2 }, { 9884, 2 }, { 9914, 5 }, { 10004, 4 }, { 10033, 6 }, { 10052, 2 }, { 10086, 2 }, { 10168, 2 }, { 10176, 1 }, { 10228, 2 }, { 10312, 2 }, { 10372, 2 }, { 10622, 2 }, { 10685, 2 }, { 10687, 1 }, { 10787, 2 }, { 11010, 2 }, + { 11024, 2 }, { 11044, 2 }, { 11086, 2 }, { 11149, 1 }, { 11198, 2 }, { 11265, 2 }, { 11302, 2 }, { 11326, 2 }, { 11354, 2 }, { 11404, 1 }, { 11473, 2 }, { 11506, 2 }, { 11548, 4 }, { 11575, 2 }, { 11621, 4 }, { 11625, 3 }, { 11625, 1 }, { 11642, 4 }, { 11859, 5 }, { 11870, 2 }, { 11872, 3 }, { 11880, 7 }, { 11886, 3 }, + { 11905, 6 }, { 11880, 3 }, { 11912, 6 }, { 11916, 4 }, { 11916, 3 }, { 11965, 4 }, { 12068, 13 }, { 12106, 2 }, { 12120, 2 }, { 12221, 2 }, { 12257, 2 }, { 12361, 2 }, { 12411, 2 }, { 12473, 3 }, { 12554, 2 }, { 12583, 2 }, { 12654, 2 }, { 12665, 2 }, { 12744, 1 }, { 12775, 2 }, { 12858, 2 }, { 12993, 2 }, { 13007, 3 }, + { 13025, 4 }, { 13038, 2 }, { 13092, 4 }, { 13094, 5 }, { 13095, 1 }, { 13110, 2 }, { 13116, 1 }, { 13140, 2 }, { 13169, 1 }, { 13186, 2 }, { 13202, 2 }, { 13202, 1 }, { 13256, 2 }, { 13344, 2 }, { 13373, 2 }, { 13396, 3 }, { 13446, 2 }, { 13451, 3 }, { 13475, 2 }, { 13521, 1 }, { 13587, 2 }, { 13592, 2 }, { 13708, 3 }, + { 13711, 1 }, { 13741, 1 }, { 13757, 1 }, { 13847, 2 }, { 13881, 3 }, { 13915, 1 }, { 14005, 2 }, { 14028, 2 }, { 14037, 2 }, { 14074, 2 }, { 14135, 2 }, { 14176, 2 }, { 14227, 2 }, { 14228, 2 }, { 14271, 3 }, { 14279, 3 }, { 14493, 2 }, { 14535, 3 }, { 14535, 1 }, { 14680, 2 }, { 14717, 2 }, { 14725, 1 }, { 14790, 2 }, + { 14801, 1 }, { 14959, 2 }, { 15052, 2 }, { 15055, 1 }, { 15055, 1 }, { 15075, 2 }, { 15103, 8 }, { 15153, 16 }, { 15191, 2 }, { 15240, 2 }, { 15313, 2 }, { 15323, 2 }, { 15341, 1 }, { 15383, 2 }, { 15387, 2 }, { 15491, 2 }, { 15534, 2 }, { 15539, 2 }, { 15549, 2 }, { 15554, 1 }, { 15664, 1 }, { 15726, 2 }, { 15807, 2 }, + { 15842, 2 }, { 15897, 2 }, { 15913, 3 }, { 15925, 2 }, { 15935, 2 }, { 16131, 1 }, { 16211, 3 }, { 16249, 2 }, { 16268, 2 }, { 16307, 2 }, { 16398, 2 }, { 16498, 2 }, { 16518, 1 }, { 16552, 1 }, { 16571, 2 }, { 16592, 2 }, { 16601, 3 }, { 16638, 2 }, { 16698, 2 }, { 16712, 1 }, { 16767, 2 }, { 16789, 2 }, { 16992, 2 }, + { 17015, 2 }, { 17035, 2 }, { 17074, 3 }, { 17086, 3 }, { 17086, 1 }, { 17092, 1 }, { 17110, 4 }, { 17116, 3 }, { 17236, 2 }, { 17291, 2 }, { 17291, 2 }, { 17340, 2 }, { 17342, 1 }, { 17360, 3 }, { 17436, 3 }, { 17457, 2 }, { 17508, 1 }, { 17556, 2 }, { 17601, 2 }, { 17639, 2 }, { 17671, 2 }, { 17743, 2 }, { 17857, 2 }, + { 17915, 2 }, { 17992, 2 }, { 18077, 1 }, { 18088, 2 }, { 18158, 1 }, { 18239, 16 }, { 18242, 2 }, { 18252, 3 }, { 18299, 1 }, { 18405, 2 }, { 18433, 2 }, { 18444, 2 }, { 18490, 2 }, { 18497, 2 }, { 18516, 2 }, { 18540, 2 }, { 18598, 2 }, { 18649, 2 }, { 18658, 2 }, { 18683, 2 }, { 18728, 2 }, { 18767, 1 }, { 18821, 2 }, + { 18868, 2 }, { 18876, 2 }, { 18914, 14 }, { 19212, 1 }, { 19215, 1 }, { 19293, 2 }, { 19303, 2 }, { 19336, 2 }, { 19376, 2 }, { 19419, 2 }, { 19558, 2 }, { 19559, 1 }, { 19609, 2 }, { 19688, 2 }, { 19724, 2 }, { 19820, 1 }, { 19851, 2 }, { 19881, 2 }, { 19966, 2 }, { 19983, 3 }, { 19988, 4 }, { 20047, 1 }, { 20062, 2 }, + { 20091, 1 }, { 20152, 1 }, { 20183, 1 }, { 20208, 2 }, { 20346, 2 }, { 20386, 1 }, { 20459, 2 }, { 20505, 2 }, { 20520, 1 }, { 20560, 3 }, { 20566, 3 }, { 20566, 1 }, { 20610, 2 }, { 20652, 2 }, { 20694, 2 }, { 20740, 2 }, { 20756, 2 }, { 20825, 3 }, { 20895, 2 }, { 20959, 1 }, { 20995, 2 }, { 21017, 3 }, { 21039, 2 }, + { 21086, 1 }, { 21109, 3 }, { 21139, 3 }, { 21206, 2 }, { 21230, 2 }, { 21251, 3 }, { 21352, 2 }, { 21353, 2 }, { 21370, 3 }, { 21389, 1 }, { 21445, 3 }, { 21475, 2 }, { 21528, 2 }, { 21559, 3 }, { 21604, 2 }, { 21606, 1 }, { 21815, 2 }, { 21858, 3 }, { 21860, 3 }, { 22015, 2 }, { 22065, 2 }, { 22098, 5 }, { 22105, 2 }, + { 22158, 3 }, { 22197, 2 }, { 22254, 1 }, { 22353, 2 }, { 22404, 4 }, { 22422, 2 }, { 22569, 2 }, { 22634, 2 }, { 22639, 2 }, { 22861, 2 }, { 22868, 2 }, { 22876, 1 }, { 22902, 2 }, { 22925, 2 }, { 23080, 2 }, { 23085, 3 }, { 23089, 5 }, { 23329, 1 }, { 23349, 2 }, { 23559, 5 }, { 23567, 3 }, { 23574, 2 }, { 23584, 3 }, + { 23615, 3 }, { 23633, 2 }, { 23674, 2 }, { 23678, 1 }, { 23853, 2 }, { 23875, 2 }, { 24010, 4 }, { 24076, 2 }, { 24128, 6 }, { 24248, 2 }, { 24253, 2 }, { 24259, 1 }, { 24319, 2 }, { 24319, 1 }, { 24502, 3 }, { 24666, 2 }, { 24781, 3 }, { 24792, 2 }, { 24909, 2 }, { 24993, 2 }, { 25039, 1 }, { 25090, 3 }, { 25137, 1 }, + { 25138, 3 }, { 25140, 3 }, { 25155, 5 }, { 25411, 2 }, { 25460, 2 }, { 25564, 3 }, { 25586, 3 }, { 25630, 2 }, { 25765, 2 }, { 25789, 3 }, { 25803, 2 }, { 25851, 2 }, { 25872, 2 }, { 25887, 2 }, { 25981, 1 }, { 26016, 2 }, { 26019, 1 }, { 26029, 1 }, { 26104, 7 }, { 26144, 2 }, { 26275, 1 }, { 26295, 2 }, { 26298, 1 }, + { 26322, 2 }, { 26380, 2 }, { 26408, 4 }, { 26446, 1 }, { 26553, 1 }, { 26576, 1 }, { 26635, 1 }, { 26668, 2 }, { 26675, 2 }, { 26698, 4 }, { 26748, 9 }, { 26788, 2 }, { 26932, 2 }, { 26962, 2 }, { 27042, 2 }, { 27060, 2 }, { 27163, 3 }, { 27202, 2 }, { 27290, 2 }, { 27337, 3 }, { 27376, 2 }, { 27439, 2 }, { 27458, 4 }, + { 27515, 2 }, { 27518, 1 }, { 27541, 2 }, { 27585, 3 }, { 27633, 2 }, { 27695, 2 }, { 27702, 2 }, { 27861, 2 }, { 27924, 1 }, { 28025, 14 }, { 28058, 2 }, { 28143, 2 }, { 28215, 2 }, { 28240, 2 }, { 28241, 2 }, { 28285, 2 }, { 28324, 3 }, { 28378, 2 }, { 28514, 2 }, { 28529, 2 }, { 28538, 2 }, { 28565, 3 }, { 28697, 2 }, + { 28735, 2 }, { 28769, 2 }, { 28770, 4 }, { 28788, 4 }, { 28807, 3 }, { 28807, 4 }, { 28829, 1 }, { 28853, 2 }, { 28856, 7 }, { 28864, 2 }, { 28865, 3 }, { 28915, 2 }, { 28928, 2 }, { 28964, 2 }, { 28988, 1 }, { 29031, 2 }, { 29095, 2 }, { 29189, 2 }, { 29205, 1 }, { 29230, 1 }, { 29332, 2 }, { 29339, 2 }, { 29349, 5 }, + { 29449, 2 }, { 29471, 2 }, { 29578, 2 }, { 29859, 2 }, { 29878, 2 }, { 29947, 10 }, { 30083, 2 }, { 30121, 2 }, { 30128, 2 }, { 30155, 4 }, { 30157, 1 }, { 30272, 2 }, { 30281, 2 }, { 30286, 2 }, { 30305, 2 }, { 30408, 2 }, { 30444, 22 }, { 30612, 2 }, { 30628, 2 }, { 30747, 2 }, { 30783, 2 }, { 30808, 5 }, { 30868, 3 }, + { 30875, 2 }, { 30997, 2 }, { 31000, 2 }, { 31022, 3 }, { 31111, 1 }, { 31144, 2 }, { 31146, 3 }, { 31187, 2 }, { 31324, 2 }, { 31343, 2 }, { 31416, 2 }, { 31485, 2 }, { 31539, 2 }, { 31638, 2 }, { 31648, 2 }, { 31750, 2 }, { 31754, 2 }, { 31785, 10 }, { 31786, 5 }, { 31800, 2 }, { 31801, 4 }, { 31807, 7 }, { 31807, 3 }, + { 31807, 10 }, { 31808, 3 }, { 31808, 4 }, { 31818, 6 }, { 31825, 7 }, { 31838, 2 }, { 31911, 1 }, { 31974, 2 }, { 32010, 3 }, { 32031, 2 }, { 32040, 2 }, { 32063, 1 }, { 32078, 2 }, { 32156, 2 }, { 32198, 31 }, { 32257, 2 }, { 32257, 2 }, { 32265, 2 }, { 32330, 2 }, { 32369, 8 }, { 32404, 3 }, { 32425, 2 }, { 32432, 2 }, + { 32505, 2 }, { 32531, 2 }, { 32536, 2 }, { 32549, 2 }, { 32582, 3 }, { 32590, 4 }, { 32624, 2 }, { 32644, 2 }, { 32692, 2 }, { 32695, 4 }, { 32699, 3 }, { 32726, 4 }, { 32784, 2 }, { 32832, 2 }, { 32883, 6 }, { 32965, 4 }, { 33044, 2 }, { 33104, 2 }, { 33184, 2 }, { 33264, 1 }, { 33292, 2 }, { 33312, 1 }, { 33468, 2 }, + { 33471, 1 }, { 33565, 2 }, { 33627, 2 }, { 33659, 2 }, { 33709, 2 }, { 33766, 5 }, { 33836, 2 }, { 33875, 2 }, { 33954, 2 }, { 33959, 2 }, { 34050, 2 }, { 34090, 2 }, { 34168, 2 }, { 34233, 2 }, { 34461, 2 }, { 34462, 1 }, { 34463, 2 }, { 34472, 4 }, { 34500, 2 }, { 34520, 2 }, { 34544, 2 }, { 34614, 2 }, { 34662, 1 }, + { 34676, 2 }, { 34729, 4 }, { 34803, 2 }, { 34845, 2 }, { 34913, 2 }, { 34963, 6 }, { 35019, 2 }, { 35022, 2 }, { 35070, 2 }, { 35120, 2 }, { 35132, 2 }, { 35144, 2 }, { 35205, 2 }, { 35230, 3 }, { 35244, 2 }, { 35271, 4 }, { 35276, 2 }, { 35282, 2 }, { 35324, 3 }, { 35366, 3 }, { 35659, 2 }, { 35680, 2 }, { 35744, 2 }, + { 35758, 3 }, { 35796, 2 }, { 35830, 2 }, { 35841, 7 }, { 35843, 2 }, { 35856, 2 }, { 35914, 4 }, { 35929, 13 }, { 35993, 2 }, { 35997, 1 }, { 36046, 4 }, { 36046, 1 }, { 36051, 1 }, { 36111, 2 }, { 36208, 1 }, { 36208, 1 }, { 36306, 2 }, { 36325, 2 }, { 36386, 2 }, { 36405, 2 }, { 36443, 1 }, { 36455, 1 }, { 36538, 2 }, + { 36562, 2 }, { 36566, 2 }, { 36628, 2 }, { 36693, 2 }, { 36713, 2 }, { 36730, 2 }, { 36747, 2 }, { 36786, 2 }, { 36810, 1 }, { 36848, 2 }, { 36914, 1 }, { 36920, 2 }, { 36952, 2 }, { 37071, 2 }, { 37086, 1 }, { 37094, 3 }, { 37158, 3 }, { 37231, 2 }, { 37241, 2 }, { 37285, 2 }, { 37349, 2 }, { 37404, 2 }, { 37410, 1 }, + { 37433, 4 }, { 37615, 2 }, { 37659, 2 }, { 37742, 2 }, { 37773, 2 }, { 37867, 1 }, { 37890, 2 }, { 37960, 2 }, { 38042, 3 }, { 38241, 2 }, { 38400, 2 }, { 38461, 1 }, { 38551, 2 }, { 38611, 1 }, { 38657, 2 }, { 38729, 2 }, { 38748, 2 }, { 38815, 2 }, { 38852, 2 }, { 38890, 1 }, { 38954, 2 }, { 39119, 2 }, { 39162, 2 }, + { 39175, 3 }, { 39176, 2 }, { 39231, 2 }, { 39261, 2 }, { 39467, 2 }, { 39500, 2 }, { 39507, 2 }, { 39566, 2 }, { 39608, 2 }, { 39686, 6 }, { 39730, 2 }, { 39842, 1 }, { 39853, 1 }, { 39905, 2 }, { 39931, 2 }, { 39989, 2 }, { 40030, 2 }, { 40227, 2 }, { 40268, 2 }, { 40372, 2 }, { 40415, 1 }, { 40488, 3 }, { 40536, 2 }, + { 40676, 3 }, { 40677, 2 }, { 40755, 2 }, { 40842, 2 }, { 40849, 1 }, { 40870, 3 }, { 40873, 3 }, { 40972, 2 }, { 41033, 2 }, { 41190, 2 }, { 41273, 5 }, { 41273, 1 }, { 41293, 2 }, { 41367, 32 }, { 41376, 2 }, { 41420, 2 }, { 41473, 2 }, { 41473, 2 }, { 41493, 4 }, { 41521, 2 }, { 41533, 2 }, { 41554, 2 }, { 41568, 2 }, + { 41583, 3 }, { 41728, 2 }, { 41786, 2 }, { 41836, 1 }, { 41875, 2 }, { 41933, 2 }, { 42044, 2 }, { 42075, 2 }, { 42076, 2 }, { 42133, 2 }, { 42259, 29 }, { 42269, 3 }, { 42294, 2 }, { 42420, 2 }, { 42524, 2 }, { 42524, 1 }, { 42546, 1 }, { 42631, 2 }, { 42693, 2 }, { 42740, 2 }, { 42744, 4 }, { 42755, 1 }, { 42870, 2 }, + { 42894, 2 }, { 42939, 2 }, { 42973, 2 }, { 43016, 2 }, { 43070, 2 }, { 43105, 2 }, { 43115, 2 }, { 43375, 3 }, { 43387, 1 }, { 43424, 3 }, { 43448, 2 }, { 43480, 2 }, { 43498, 2 }, { 43651, 2 }, { 43727, 2 }, { 43879, 2 }, { 43910, 1 }, { 43977, 2 }, { 44003, 2 }, { 44080, 2 }, { 44082, 1 }, { 44136, 2 }, { 44169, 29 }, + { 44186, 2 }, { 44339, 2 }, { 44350, 1 }, { 44356, 1 }, { 44430, 2 }, { 44440, 1 }, { 44530, 1 }, { 44538, 2 }, { 44572, 2 }, { 44585, 2 }, { 44709, 2 }, { 44748, 2 }, { 44748, 2 }, { 44769, 2 }, { 44813, 2 }, { 44890, 2 }, { 45015, 2 }, { 45046, 4 }, { 45052, 2 }, { 45062, 2 }, { 45094, 6 }, { 45184, 2 }, { 45191, 2 }, + { 45201, 3 }, { 45216, 3 }, { 45227, 2 }, { 45269, 1 }, { 45294, 2 }, { 45314, 2 }, { 45345, 8 }, { 45352, 2 }, { 45365, 3 }, { 45378, 1 }, { 45392, 4 }, { 45405, 3 }, { 45410, 2 }, { 45448, 14 }, { 45450, 2 }, { 45457, 2 }, { 45466, 3 }, { 45481, 4 }, { 45486, 7 }, { 45533, 5 }, { 45576, 2 }, { 45649, 2 }, { 45917, 2 }, + { 45919, 6 }, { 45919, 1 }, { 45930, 15 }, { 45930, 2 }, { 46001, 5 }, { 46036, 2 }, { 46054, 2 }, { 46075, 2 }, { 46153, 2 }, { 46155, 2 }, { 46228, 2 }, { 46234, 2 }, { 46273, 2 }, { 46387, 2 }, { 46398, 2 }, { 46517, 2 }, { 46559, 2 }, { 46565, 1 }, { 46598, 2 }, { 46686, 2 }, { 46744, 2 }, { 46816, 3 }, { 46835, 2 }, + { 46921, 2 }, { 46938, 2 }, { 46991, 2 }, { 47038, 2 }, { 47098, 3 }, { 47107, 2 }, { 47201, 3 }, { 47327, 1 }, { 47327, 1 }, { 47338, 2 }, { 47395, 1 }, { 47499, 2 }, { 47504, 2 }, { 47515, 1 }, { 47516, 1 }, { 47600, 1 }, { 47604, 1 }, { 47707, 1 }, { 47728, 1 }, { 47748, 2 }, { 47763, 2 }, { 47807, 4 }, { 47814, 2 }, + { 47822, 2 }, { 47834, 2 }, { 47843, 3 }, { 47886, 2 }, { 47893, 2 }, { 48066, 2 }, { 48126, 2 }, { 48133, 1 }, { 48166, 2 }, { 48299, 1 }, { 48455, 2 }, { 48468, 2 }, { 48568, 2 }, { 48606, 2 }, { 48642, 2 }, { 48698, 2 }, { 48714, 2 }, { 48754, 2 }, { 48765, 3 }, { 48773, 5 }, { 48819, 2 }, { 48833, 2 }, { 48904, 2 }, + { 49000, 1 }, { 49113, 12 }, { 49140, 2 }, { 49276, 2 }, { 49353, 2 }, { 49411, 3 }, { 49418, 2 }, { 49540, 2 }, { 49544, 2 }, { 49584, 2 }, { 49602, 2 }, { 49784, 5 }, { 49822, 4 }, { 49822, 5 }, { 49828, 2 }, { 49866, 2 }, { 49922, 3 }, { 49959, 2 }, { 50045, 2 }, { 50134, 3 }, { 50140, 2 }, { 50237, 2 }, { 50247, 2 }, + { 50266, 13 }, { 50290, 2 }, { 50312, 4 }, { 50314, 1 }, { 50527, 2 }, { 50605, 1 }, { 50730, 2 }, { 50751, 2 }, { 50770, 2 }, { 50858, 2 }, { 50859, 2 }, { 50909, 2 }, { 50948, 3 }, { 51043, 2 }, { 51048, 2 }, { 51089, 2 }, { 51090, 2 }, { 51141, 2 }, { 51163, 2 }, { 51250, 2 }, { 51347, 2 }, { 51475, 2 }, { 51536, 2 }, + { 51544, 2 }, { 51595, 2 }, { 51602, 19 }, { 51643, 5 }, { 51702, 2 }, { 51702, 10 }, { 51764, 2 }, { 51793, 5 }, { 51812, 2 }, { 51839, 1 }, { 51938, 3 }, { 51941, 1 }, { 51967, 4 }, { 52049, 3 }, { 52074, 3 }, { 52098, 2 }, { 52118, 2 }, { 52119, 3 }, { 52227, 11 }, { 52246, 3 }, { 52282, 2 }, { 52451, 2 }, { 52583, 2 }, + { 52601, 1 }, { 52605, 2 }, { 52615, 2 }, { 52668, 2 }, { 52824, 2 }, { 53076, 1 }, { 53120, 1 }, { 53179, 2 }, { 53189, 2 }, { 53193, 1 }, { 53195, 2 }, { 53246, 2 }, { 53249, 2 }, { 53268, 1 }, { 53295, 2 }, { 53312, 2 }, { 53410, 2 }, { 53451, 2 }, { 53570, 2 }, { 53593, 2 }, { 53635, 2 }, { 53657, 2 }, { 53682, 3 }, + { 53728, 5 }, { 53733, 2 }, { 53753, 2 }, { 53787, 4 }, { 53807, 1 }, { 54008, 2 }, { 54059, 2 }, { 54060, 1 }, { 54080, 2 }, { 54090, 1 }, { 54138, 2 }, { 54149, 2 }, { 54168, 1 }, { 54171, 2 }, { 54216, 22 }, { 54233, 6 }, { 54434, 2 }, { 54534, 2 }, { 54562, 2 }, { 54763, 2 }, { 54791, 2 }, { 54816, 2 }, { 54909, 2 }, + { 54916, 3 }, { 54963, 2 }, { 54985, 2 }, { 54991, 3 }, { 55016, 3 }, { 55025, 3 }, { 55032, 2 }, { 55099, 2 }, { 55260, 2 }, { 55261, 2 }, { 55270, 3 }, { 55384, 2 }, { 55455, 2 }, { 55456, 2 }, { 55504, 3 }, { 55510, 2 }, { 55558, 2 }, { 55568, 2 }, { 55585, 2 }, { 55677, 2 }, { 55703, 2 }, { 55749, 2 }, { 55779, 2 }, + { 55789, 3 }, { 55792, 2 }, { 55830, 4 }, { 55835, 2 }, { 55879, 2 }, { 56076, 2 }, { 56118, 2 }, { 56314, 2 }, { 56392, 1 }, { 56411, 2 }, { 56459, 2 }, { 56553, 34 }, { 56575, 2 }, { 56733, 2 }, { 56762, 2 }, { 56793, 3 }, { 56877, 3 }, { 56927, 2 }, { 56981, 2 }, { 57014, 1 }, { 57149, 2 }, { 57162, 2 }, { 57186, 2 }, + { 57254, 2 }, { 57267, 1 }, { 57324, 2 }, { 57327, 2 }, { 57365, 4 }, { 57371, 2 }, { 57445, 2 }, { 57477, 2 }, { 57497, 2 }, { 57536, 2 }, { 57609, 2 }, { 57626, 2 }, { 57666, 2 }, { 57694, 2 }, { 57694, 2 }, { 57749, 2 }, { 57781, 7 }, { 57878, 2 }, { 57953, 2 }, { 58051, 2 }, { 58088, 2 }, { 58097, 2 }, { 58142, 3 }, + { 58142, 1 }, { 58197, 1 }, { 58221, 2 }, { 58222, 2 }, { 58244, 2 }, { 58290, 1 }, { 58296, 1 }, { 58325, 2 }, { 58378, 1 }, { 58389, 3 }, { 58430, 2 }, { 58454, 2 }, { 58551, 29 }, { 58563, 6 }, { 58681, 2 }, { 58751, 8 }, { 58752, 43 }, { 58790, 5 }, { 58846, 2 }, { 58879, 6 }, { 58953, 2 }, { 58998, 2 }, { 59010, 1 }, + { 59038, 5 }, { 59135, 2 }, { 59166, 2 }, { 59180, 2 }, { 59222, 2 }, { 59227, 2 }, { 59307, 2 }, { 59398, 3 }, { 59411, 2 }, { 59436, 3 }, { 59464, 2 }, { 59569, 2 }, { 59587, 2 }, { 59624, 3 }, { 59786, 2 }, { 59834, 2 }, { 59841, 2 }, { 59841, 1 }, { 59984, 2 }, { 59985, 2 }, { 60003, 3 }, { 60045, 2 }, { 60097, 2 }, + { 60148, 2 }, { 60172, 2 }, { 60203, 5 }, { 60565, 2 }, { 60625, 2 }, { 60743, 2 }, { 60781, 2 }, { 60892, 2 }, { 60977, 2 }, { 60979, 2 }, { 61021, 5 }, { 61021, 4 }, { 61026, 2 }, { 61139, 2 }, { 61165, 3 }, { 61204, 2 }, { 61207, 1 }, { 61248, 3 }, { 61257, 2 }, { 61264, 6 }, { 61272, 3 }, { 61410, 2 }, { 61410, 3 }, + { 61416, 2 }, { 61423, 1 }, { 61503, 2 }, { 61503, 2 }, { 61533, 2 }, { 61567, 2 }, { 61575, 2 }, { 61835, 1 }, { 61842, 1 }, { 61924, 2 }, { 61951, 6 }, { 61975, 2 }, { 61986, 3 }, { 62024, 1 }, { 62110, 2 }, { 62135, 2 }, { 62192, 2 }, { 62208, 2 }, { 62399, 2 }, { 62400, 1 }, { 62414, 2 }, { 62423, 3 }, { 62456, 3 }, + { 62459, 3 }, { 62478, 3 }, { 62484, 2 }, { 62510, 6 }, { 62511, 3 }, { 62565, 3 }, { 62610, 2 }, { 62875, 4 }, { 62896, 5 }, { 62898, 2 }, { 62904, 2 }, { 62938, 3 }, { 62943, 2 }, { 62977, 2 }, { 62989, 3 }, { 62998, 5 }, { 63069, 1 }, { 63093, 5 }, { 63107, 2 }, { 63113, 1 }, { 63231, 4 }, { 63253, 2 }, { 63286, 4 }, + { 63289, 2 }, { 63334, 1 }, { 63334, 4 }, { 63413, 2 }, { 63425, 2 }, { 63512, 10 }, { 63537, 1 }, { 63694, 1 }, { 63721, 4 }, { 63749, 2 }, { 63783, 17 }, { 63791, 3 }, { 63792, 2 }, { 63882, 25 }, { 63896, 1 }, { 63936, 2 }, { 63969, 3 }, { 63986, 2 }, { 63988, 2 }, { 64009, 10 }, { 64018, 2 }, { 64032, 6 }, { 64125, 2 }, + { 64195, 1 }, { 64221, 7 }, { 64390, 2 }, { 64459, 2 }, { 64568, 2 }, { 64784, 1 }, { 64789, 2 }, { 64829, 2 }, { 64848, 1 }, { 64914, 2 }, { 64928, 1 }, { 64939, 2 }, { 65026, 2 }, { 65057, 2 }, { 65070, 2 }, { 65193, 4 }, { 65235, 3 }, { 65242, 2 }, { 65281, 2 }, { 65320, 2 }, { 65365, 1 }, { 65414, 2 }, { 65445, 2 }, + { 65581, 2 }, { 65624, 1 }, { 65719, 2 }, { 65766, 2 }, { 65927, 2 }, { 66004, 1 }, { 66031, 2 }, { 66085, 1 }, { 66085, 2 }, { 66133, 2 }, { 66134, 2 }, { 66188, 1 }, { 66240, 2 }, { 66249, 2 }, { 66250, 2 }, { 66295, 2 }, { 66342, 1 }, { 66352, 3 }, { 66388, 3 }, { 66432, 2 }, { 66437, 47 }, { 66497, 2 }, { 66517, 2 }, + { 66526, 2 }, { 66546, 9 }, { 66605, 2 }, { 66753, 2 }, { 66792, 2 }, { 66796, 2 }, { 66828, 2 }, { 66899, 3 }, { 66970, 6 }, { 66981, 2 }, { 66983, 1 }, { 67009, 2 }, { 67017, 4 }, { 67115, 2 }, { 67117, 1 }, { 67130, 6 }, { 67132, 7 }, { 67162, 2 }, { 67179, 6 }, { 67236, 2 }, { 67263, 3 }, { 67274, 2 }, { 67274, 2 }, + { 67349, 3 }, { 67486, 2 }, { 67503, 3 }, { 67517, 1 }, { 67559, 1 }, { 67660, 2 }, { 67727, 2 }, { 67901, 2 }, { 67943, 4 }, { 67950, 2 }, { 67965, 3 }, { 68029, 2 }, { 68048, 2 }, { 68169, 2 }, { 68172, 1 }, { 68258, 2 }, { 68288, 1 }, { 68359, 2 }, { 68441, 2 }, { 68484, 2 }, { 68488, 2 }, { 68525, 2 }, { 68535, 2 }, + { 68575, 7 }, { 68575, 5 }, { 68583, 2 }, { 68588, 4 }, { 68593, 1 }, { 68597, 2 }, { 68636, 2 }, { 68636, 2 }, { 68667, 2 }, { 68785, 1 }, { 68914, 4 }, { 68915, 5 }, { 68940, 3 }, { 69010, 2 }, { 69063, 2 }, { 69076, 2 }, { 69235, 2 }, { 69270, 2 }, { 69298, 1 }, { 69350, 5 }, { 69432, 2 }, { 69514, 2 }, { 69562, 3 }, + { 69562, 4 }, { 69638, 1 }, { 69656, 2 }, { 69709, 2 }, { 69775, 2 }, { 69788, 2 }, { 70193, 2 }, { 70233, 2 }, { 70252, 2 }, { 70259, 2 }, { 70293, 3 }, { 70405, 3 }, { 70462, 2 }, { 70515, 3 }, { 70518, 2 }, { 70535, 6 }, { 70547, 6 }, { 70577, 6 }, { 70631, 17 }, { 70667, 2 }, { 70680, 1 }, { 70694, 1 }, { 70898, 2 }, + { 70916, 1 }, { 70936, 3 }, { 71033, 2 }, { 71126, 2 }, { 71158, 2 }, { 71162, 2 }, { 71421, 1 }, { 71441, 2 }, { 71557, 2 }, { 71789, 1 }, { 71816, 2 }, { 71850, 1 }, { 71869, 1 }, { 71961, 2 }, { 71973, 4 }, { 72064, 2 }, { 72110, 2 }, { 72117, 3 }, { 72164, 2 }, { 72266, 2 }, { 72325, 2 }, { 72326, 1 }, { 72420, 2 }, + { 72693, 2 }, { 72705, 1 }, { 72730, 2 }, { 72793, 2 }, { 72795, 1 }, { 72939, 1 }, { 72945, 3 }, { 72945, 2 }, { 73120, 1 }, { 73121, 5 }, { 73122, 4 }, { 73126, 1 }, { 73126, 1 }, { 73196, 3 }, { 73219, 2 }, { 73241, 6 }, { 73272, 3 }, { 73354, 1 }, { 73368, 2 }, { 73467, 1 }, { 73517, 2 }, { 73554, 2 }, { 73678, 2 }, + { 73838, 1 }, { 73881, 2 }, { 73958, 2 }, { 73985, 15 }, { 74092, 2 }, { 74205, 2 }, { 74245, 2 }, { 74277, 2 }, { 74286, 2 }, { 74353, 2 }, { 74403, 2 }, { 74428, 1 }, { 74468, 2 }, { 74481, 3 }, { 74511, 2 }, { 74537, 2 }, { 74596, 2 }, { 74750, 2 }, { 74754, 2 }, { 74861, 2 }, { 74933, 4 }, { 74970, 1 }, { 75003, 3 }, + { 75077, 1 }, { 75159, 2 }, { 75170, 2 }, { 75234, 2 }, { 75300, 3 }, { 75337, 2 }, { 75345, 2 }, { 75419, 1 }, { 75429, 2 }, { 75477, 1 }, { 75513, 2 }, { 75536, 2 }, { 75536, 2 }, { 75539, 1 }, { 75551, 2 }, { 75561, 2 }, { 75565, 2 }, { 75590, 2 }, { 75623, 5 }, { 75773, 6 }, { 75777, 6 }, { 75785, 2 }, { 75791, 2 }, + { 75804, 2 }, { 75862, 2 }, { 75924, 3 }, { 75927, 2 }, { 75996, 11 }, { 76000, 1 }, { 76006, 2 }, { 76020, 3 }, { 76110, 2 }, { 76126, 3 }, { 76131, 2 }, { 76136, 2 }, { 76144, 2 }, { 76203, 2 }, { 76229, 3 }, { 76244, 15 }, { 76246, 2 }, { 76300, 1 }, { 76403, 3 }, { 76545, 2 }, { 76569, 2 }, { 76813, 2 }, { 76821, 2 }, + { 76837, 2 }, { 76863, 2 }, { 77027, 2 }, { 77037, 2 }, { 77074, 3 }, { 77170, 2 }, { 77191, 2 }, { 77220, 2 }, { 77230, 2 }, { 77261, 2 }, { 77277, 2 }, { 77309, 2 }, { 77314, 2 }, { 77412, 2 }, { 77419, 2 }, { 77457, 2 }, { 77633, 3 }, { 77714, 2 }, { 77855, 2 }, { 77857, 1 }, { 77876, 2 }, { 77895, 2 }, { 77916, 5 }, + { 77947, 2 }, { 77948, 1 }, { 77966, 1 }, { 77996, 2 }, { 78025, 1 }, { 78064, 2 }, { 78100, 2 }, { 78113, 1 }, { 78114, 3 }, { 78167, 2 }, { 78175, 2 }, { 78260, 2 }, { 78261, 1 }, { 78265, 2 }, { 78286, 1 }, { 78300, 2 }, { 78327, 3 }, { 78363, 1 }, { 78384, 2 }, { 78459, 2 }, { 78516, 2 }, { 78612, 2 }, { 78643, 2 }, + { 78655, 2 }, { 78698, 1 }, { 78720, 3 }, { 78789, 3 }, { 78838, 5 }, { 78893, 1 }, { 78954, 7 }, { 79007, 2 }, { 79132, 3 }, { 79193, 2 }, { 79193, 2 }, { 79226, 2 }, { 79411, 2 }, { 79422, 1 }, { 79502, 2 }, { 79593, 2 }, { 79622, 2 }, { 79657, 3 }, { 79771, 2 }, { 79866, 2 }, { 79909, 2 }, { 80005, 2 }, { 80032, 2 }, + { 80060, 1 }, { 80132, 2 }, { 80149, 3 }, { 80251, 2 }, { 80363, 2 }, { 80379, 1 }, { 80464, 2 }, { 80498, 2 }, { 80553, 2 }, { 80556, 3 }, { 80559, 1 }, { 80571, 2 }, { 80652, 1 }, { 80703, 2 }, { 80754, 2 }, { 80754, 2 }, { 80860, 2 }, { 81055, 2 }, { 81087, 4 }, { 81210, 2 }, { 81211, 1 }, { 81216, 1 }, { 81223, 1 }, + { 81231, 1 }, { 81288, 2 }, { 81317, 2 }, { 81327, 3 }, { 81332, 2 }, { 81376, 2 }, { 81469, 2 }, { 81579, 2 }, { 81617, 1 }, { 81630, 2 }, { 81666, 2 }, { 81800, 2 }, { 81832, 2 }, { 81848, 2 }, { 81869, 2 }, { 81941, 3 }, { 82177, 3 }, { 82179, 2 }, { 82180, 2 }, { 82182, 4 }, { 82185, 2 }, { 82195, 2 }, { 82238, 4 }, + { 82265, 3 }, { 82295, 10 }, { 82299, 9 }, { 82367, 3 }, { 82379, 3 }, { 82380, 1 }, { 82505, 2 }, { 82568, 2 }, { 82620, 1 }, { 82637, 5 }, { 82821, 2 }, { 82841, 2 }, { 82945, 1 }, { 83020, 12 }, { 83072, 2 }, { 83181, 2 }, { 83240, 2 }, { 83253, 3 }, { 83261, 2 }, { 83288, 2 }, { 83291, 4 }, { 83295, 3 }, { 83365, 2 }, + { 83368, 2 }, { 83408, 2 }, { 83458, 2 }, { 83470, 2 }, { 83471, 1 }, { 83637, 3 }, { 83693, 2 }, { 83703, 2 }, { 83732, 2 }, { 83745, 1 }, { 83800, 4 }, { 83801, 3 }, { 83856, 3 }, { 83863, 5 }, { 83867, 2 }, { 83868, 3 }, { 83898, 7 }, { 83900, 4 }, { 83901, 5 }, { 83989, 2 }, { 84049, 35 }, { 84086, 2 }, { 84089, 2 }, + { 84115, 3 }, { 84130, 3 }, { 84132, 2 }, { 84143, 3 }, { 84173, 2 }, { 84185, 5 }, { 84297, 2 }, { 84390, 2 }, { 84497, 4 }, { 84657, 2 }, { 84657, 2 }, { 84724, 2 }, { 84775, 2 }, { 84870, 2 }, { 84892, 2 }, { 84910, 3 }, { 84935, 3 }, { 85002, 2 }, { 85051, 2 }, { 85052, 2 }, { 85135, 25 }, { 85135, 2 }, { 85144, 2 }, + { 85165, 3 }, { 85205, 2 }, { 85232, 2 }, { 85281, 5 }, { 85423, 6 }, { 85539, 2 }, { 85582, 4 }, { 85609, 2 }, { 85701, 36 }, { 85705, 2 }, { 85824, 2 }, { 85824, 2 }, { 85858, 30 }, { 85858, 28 }, { 85904, 35 }, { 85910, 2 }, { 85913, 2 }, { 85926, 3 }, { 85942, 4 }, { 85969, 4 }, { 85996, 1 }, { 86013, 3 }, { 86034, 13 }, + { 86068, 8 }, { 86069, 8 }, { 86089, 8 }, { 86193, 13 }, { 86217, 7 }, { 86219, 2 }, { 86250, 2 }, { 86304, 16 }, { 86317, 2 }, { 86322, 4 }, { 86325, 2 }, { 86333, 2 }, { 86394, 2 }, { 86433, 2 }, { 86469, 3 }, { 86512, 4 }, { 86537, 2 }, { 86627, 2 }, { 86658, 2 }, { 86810, 2 }, { 86813, 2 }, { 86884, 2 }, { 86947, 2 }, + { 87003, 2 }, { 87010, 5 }, { 87019, 2 }, { 87027, 2 }, { 87105, 2 }, { 87107, 2 }, { 87183, 2 }, { 87273, 2 }, { 87358, 3 }, { 87388, 3 }, { 87503, 4 }, { 87639, 2 }, { 87649, 4 }, { 87722, 2 }, { 87829, 2 }, { 87829, 1 }, { 87863, 2 }, { 87894, 2 }, { 87988, 32 }, { 88035, 27 }, { 88059, 3 }, { 88094, 5 }, { 88111, 21 }, + { 88129, 2 }, { 88175, 5 }, { 88256, 2 }, { 88329, 2 }, { 88415, 3 }, { 88482, 2 }, { 88502, 1 }, { 88529, 2 }, { 88551, 3 }, { 88552, 1 }, { 88713, 2 }, { 88797, 2 }, { 88844, 27 }, { 88925, 5 }, { 88935, 2 }, { 88944, 1 }, { 89073, 2 }, { 89095, 3 }, { 89283, 2 }, { 89294, 3 }, { 89299, 2 }, { 89324, 2 }, { 89368, 2 }, + { 89387, 2 }, { 89464, 2 }, { 89607, 2 }, { 89737, 2 }, { 89791, 2 }, { 89794, 3 }, { 89840, 2 }, { 89849, 3 }, { 89859, 2 }, { 89905, 2 }, { 89952, 38 }, { 90030, 7 }, { 90030, 6 }, { 90031, 1 }, { 90072, 2 }, { 90090, 2 }, { 90146, 3 }, { 90202, 23 }, { 90302, 3 }, { 90328, 14 }, { 90335, 14 }, { 90338, 8 }, { 90380, 2 }, + { 90434, 1 }, { 90482, 2 }, { 90527, 9 }, { 90537, 3 }, { 90545, 2 }, { 90639, 5 }, { 90642, 2 }, { 90709, 2 }, { 90775, 1 }, { 90806, 2 }, { 90845, 19 }, { 90872, 4 }, { 90884, 2 }, { 90910, 2 }, { 90994, 5 }, { 91046, 8 }, { 91059, 8 }, { 91096, 39 }, { 91147, 2 }, { 91168, 1 }, { 91493, 2 }, { 91513, 3 }, { 91618, 3 }, + { 91653, 2 }, { 91817, 2 }, { 91831, 3 }, { 91833, 3 }, { 91885, 2 }, { 91919, 2 }, { 91934, 2 }, { 92245, 1 }, { 92284, 2 }, { 92292, 4 }, { 92369, 3 }, { 92388, 2 }, { 92426, 7 }, { 92720, 14 }, { 92720, 6 }, { 92729, 9 }, { 92733, 13 }, { 92735, 6 }, { 92786, 2 }, { 92853, 31 }, { 92906, 2 }, { 93031, 7 }, { 93077, 2 }, + { 93102, 2 }, { 93109, 2 }, { 93122, 3 }, { 93214, 2 }, { 93330, 2 }, { 93395, 2 }, { 93506, 2 }, { 93564, 9 }, { 93713, 9 }, { 93722, 4 }, { 93840, 2 }, { 93877, 4 }, { 93891, 3 }, { 93948, 2 }, { 93981, 2 }, { 94012, 3 }, { 94033, 2 }, { 94121, 2 }, { 94165, 32 }, { 94181, 3 }, { 94210, 2 }, { 94216, 2 }, { 94230, 2 }, + { 94333, 31 }, { 94433, 3 }, { 94497, 3 }, { 94609, 2 }, { 94623, 2 }, { 94763, 2 }, { 94780, 2 }, { 95287, 2 }, { 95348, 2 }, { 95433, 5 }, { 95446, 2 }, { 95493, 7 }, { 95517, 3 }, { 95580, 2 }, { 95610, 5 }, { 95620, 2 }, { 95678, 3 }, { 95683, 2 }, { 95689, 2 }, { 95760, 2 }, { 95792, 2 }, { 95850, 2 }, { 95908, 2 }, + { 95908, 2 }, { 95967, 2 }, { 96022, 3 }, { 96088, 2 }, { 96460, 2 }, { 96554, 2 }, { 96597, 2 }, { 96763, 2 }, { 96808, 2 }, { 96854, 1 }, { 96963, 1 }, { 97007, 3 }, { 97125, 1 }, { 97128, 2 }, { 97133, 3 }, { 97142, 3 }, { 97156, 2 }, { 97223, 2 }, { 97244, 2 }, { 97303, 2 }, { 97355, 2 }, { 97356, 3 }, { 97393, 3 }, + { 97409, 1 }, { 97451, 2 }, { 97539, 2 }, { 97546, 2 }, { 97553, 2 }, { 97627, 2 }, { 97640, 2 }, { 97650, 6 }, { 97675, 2 }, { 97685, 3 }, { 97773, 2 }, { 97802, 4 }, { 97826, 19 }, { 97860, 2 }, { 97956, 2 }, { 97958, 2 }, { 97973, 3 }, { 97982, 2 }, { 98039, 2 }, { 98051, 2 }, { 98059, 2 }, { 98088, 2 }, { 98092, 4 }, + { 98147, 2 }, { 98147, 2 }, { 98169, 2 }, { 98207, 2 }, { 98277, 1 }, { 98277, 22 }, { 98285, 2 }, { 98324, 3 }, { 98324, 3 }, { 98381, 31 }, { 98390, 2 }, { 98404, 2 }, { 98415, 4 }, { 98460, 2 }, { 98462, 1 }, { 98475, 3 }, { 98485, 2 }, { 98640, 1 }, { 98798, 2 }, { 98800, 4 }, { 98821, 2 }, { 98895, 2 }, { 98936, 2 }, + { 98950, 2 }, { 98980, 2 }, { 99033, 2 }, { 99045, 2 }, { 99135, 2 }, { 99315, 30 }, { 99324, 2 }, { 99346, 2 }, { 99418, 2 }, { 99505, 2 }, { 99557, 2 }, { 99559, 2 }, { 99586, 2 }, { 99622, 2 }, { 99770, 1 }, { 99790, 2 }, { 99810, 2 }, { 99871, 1 }, { 99926, 2 }, { 99927, 2 }, { 99978, 2 }, { 99980, 2 }, { 100022, 3 }, + { 100024, 1 }, { 100069, 2 }, { 100150, 2 }, { 100225, 2 }, { 100246, 1 }, { 100310, 2 }, { 100361, 2 }, { 100428, 1 }, { 100434, 2 }, { 100450, 4 }, { 100546, 2 }, { 100551, 2 }, { 100551, 2 }, { 100554, 1 }, { 100597, 2 }, { 100676, 2 }, { 100693, 2 }, { 100827, 2 }, { 100928, 2 }, { 100928, 1 }, { 100935, 2 }, { 100937, 3 }, + { 101034, 2 }, { 101041, 2 }, { 101154, 2 }, { 101200, 4 }, { 101250, 2 }, { 101352, 2 }, { 101403, 2 }, { 101430, 1 }, { 101508, 3 }, { 101509, 3 }, { 101523, 10 }, { 101604, 2 }, { 101637, 2 }, { 101681, 4 }, { 101759, 1 }, { 101773, 1 }, { 101836, 1 }, { 101882, 4 }, { 101895, 2 }, { 101897, 2 }, { 101939, 2 }, { 101951, 6 }, + { 101956, 5 }, { 102055, 1 }, { 102085, 2 }, { 102093, 2 }, { 102209, 2 }, { 102258, 6 }, { 102271, 2 }, { 102284, 2 }, { 102332, 2 }, { 102354, 2 }, { 102366, 2 }, { 102424, 3 }, { 102456, 2 }, { 102496, 1 }, { 102497, 3 }, { 102519, 3 }, { 102554, 1 }, { 102610, 5 }, { 102657, 2 }, { 102661, 4 }, { 102695, 4 }, { 102707, 12 }, + { 102910, 2 }, { 102930, 5 }, { 102937, 9 }, { 102938, 7 }, { 102965, 6 }, { 102969, 7 }, { 103031, 2 }, { 103062, 2 }, { 103096, 2 }, { 103146, 2 }, { 103159, 2 }, { 103223, 2 }, { 103267, 2 }, { 103296, 2 }, { 103303, 2 }, { 103487, 2 }, { 103491, 2 }, { 103599, 2 }, { 103677, 2 }, { 103903, 1 }, { 104040, 2 }, { 104047, 1 }, + { 104052, 2 }, { 104057, 4 }, { 104057, 2 }, { 104062, 4 }, { 104091, 2 }, { 104189, 3 }, { 104283, 8 }, { 104288, 4 }, { 104305, 3 }, { 104445, 2 }, { 104472, 2 }, { 104475, 1 }, { 104497, 4 }, { 104548, 2 }, { 104582, 2 }, { 104626, 1 }, { 104716, 2 }, { 104826, 2 }, { 104849, 2 }, { 104872, 1 }, { 104945, 1 }, { 104948, 2 }, + { 105066, 2 }, { 105071, 1 }, { 105198, 4 }, { 105198, 4 }, { 105203, 2 }, { 105256, 6 }, { 105263, 2 }, { 105329, 2 }, { 105515, 2 }, { 105566, 2 }, { 105566, 2 }, { 105585, 2 }, { 105678, 2 }, { 105852, 2 }, { 105877, 2 }, { 105911, 2 }, { 106022, 1 }, { 106033, 2 }, { 106080, 2 }, { 106192, 2 }, { 106220, 3 }, { 106243, 2 }, + { 106323, 11 }, { 106371, 2 }, { 106608, 2 }, { 106624, 2 }, { 106680, 3 }, { 106688, 1 }, { 106800, 1 }, { 106800, 1 }, { 106821, 4 }, { 106853, 1 }, { 106930, 3 }, { 106937, 2 }, { 106955, 2 }, { 106996, 2 }, { 106996, 1 }, { 107148, 4 }, { 107213, 16 }, { 107213, 2 }, { 107243, 2 }, { 107360, 2 }, { 107408, 2 }, { 107509, 4 }, + { 107572, 2 }, { 107592, 2 }, { 107644, 5 }, { 107679, 2 }, { 107705, 3 }, { 107761, 4 }, { 107780, 2 }, { 107825, 2 }, { 108007, 2 }, { 108041, 4 }, { 108058, 2 }, { 108071, 1 }, { 108132, 2 }, { 108164, 2 }, { 108189, 2 }, { 108210, 2 }, { 108330, 2 }, { 108430, 2 }, { 108450, 2 }, { 108469, 2 }, { 108484, 2 }, { 108533, 2 }, + { 108588, 2 }, { 108594, 2 }, { 108690, 2 }, { 108785, 1 }, { 108814, 2 }, { 108818, 1 }, { 108820, 2 }, { 108889, 2 }, { 108951, 2 }, { 108959, 2 }, { 108963, 2 }, { 109034, 2 }, { 109172, 1 }, { 109176, 2 }, { 109195, 3 }, { 109229, 2 }, { 109256, 2 }, { 109290, 2 }, { 109304, 2 }, { 109333, 2 }, { 109343, 4 }, { 109347, 7 }, + { 109387, 2 }, { 109421, 1 }, { 109497, 2 }, { 109501, 3 }, { 109513, 2 }, { 109525, 3 }, { 109625, 4 }, { 109710, 2 }, { 109740, 2 }, { 109751, 2 }, { 109761, 2 }, { 109890, 8 }, { 109891, 4 }, { 109909, 2 }, { 109923, 1 }, { 110017, 2 }, { 110046, 2 }, { 110111, 2 }, { 110258, 2 }, { 110340, 2 }, { 110352, 2 }, { 110398, 2 }, + { 110583, 2 }, { 110600, 13 }, { 110626, 3 }, { 110709, 2 }, { 110772, 4 }, { 110773, 2 }, { 110813, 1 }, { 110890, 2 }, { 110898, 2 }, { 110954, 2 }, { 111120, 2 }, { 111132, 3 }, { 111163, 8 }, { 111224, 2 }, { 111340, 2 }, { 111398, 2 }, { 111555, 2 }, { 111597, 3 }, { 111607, 2 }, { 111655, 2 }, { 111691, 3 }, { 111835, 2 }, + { 111854, 2 }, { 111876, 16 }, { 111884, 1 }, { 111884, 2 }, { 111929, 2 }, { 111941, 2 }, { 111969, 2 }, { 112003, 2 }, { 112165, 2 }, { 112365, 2 }, { 112450, 1 }, { 112521, 2 }, { 112649, 4 }, { 112665, 2 }, { 112881, 1 }, { 112882, 2 }, { 112906, 2 }, { 112951, 2 }, { 112994, 2 }, { 112997, 2 }, { 113002, 2 }, { 113056, 1 }, + { 113077, 2 }, { 113208, 1 }, { 113320, 2 }, { 113326, 3 }, { 113375, 2 }, { 113530, 30 }, { 113530, 30 }, { 113537, 1 }, { 113563, 14 }, { 113592, 2 }, { 113637, 2 }, { 113768, 2 }, { 113850, 5 }, { 113892, 2 }, { 113916, 2 }, { 113965, 2 }, { 113976, 2 }, { 114037, 2 }, { 114149, 1 }, { 114158, 9 }, { 114201, 2 }, { 114262, 2 }, + { 114268, 4 }, { 114353, 2 }, { 114388, 2 }, { 114404, 2 }, { 114428, 5 }, { 114438, 2 }, { 114541, 2 }, { 114550, 2 }, { 114561, 2 }, { 114625, 3 }, { 114730, 2 }, { 114770, 1 }, { 114815, 4 }, { 114998, 2 }, { 115077, 2 }, { 115093, 2 }, { 115120, 2 }, { 115194, 2 }, { 115216, 3 }, { 115299, 2 }, { 115391, 3 }, { 115410, 2 }, + { 115542, 33 }, { 115581, 2 }, { 115618, 2 }, { 115645, 5 }, { 115647, 2 }, { 115697, 2 }, { 115725, 2 }, { 115740, 2 }, { 115757, 2 }, { 115763, 2 }, { 115770, 2 }, { 115787, 2 }, { 115916, 2 }, { 115928, 2 }, { 115962, 2 }, { 116020, 2 }, { 116022, 1 }, { 116089, 2 }, { 116159, 1 }, { 116196, 2 }, { 116247, 2 }, { 116254, 7 }, + { 116336, 2 }, { 116409, 2 }, { 116459, 2 }, { 116569, 2 }, { 116619, 2 }, { 116688, 2 }, { 116733, 2 }, { 116807, 3 }, { 116843, 2 }, { 116886, 1 }, { 116902, 2 }, { 116931, 2 }, { 116952, 2 }, { 116952, 2 }, { 117177, 2 }, { 117189, 2 }, { 117206, 2 }, { 117260, 29 }, { 117271, 6 }, { 117276, 3 }, { 117276, 5 }, { 117278, 3 }, + { 117278, 2 }, { 117359, 4 }, { 117380, 2 }, { 117414, 1 }, { 117503, 2 }, { 117517, 2 }, { 117530, 2 }, { 117574, 4 }, { 117575, 5 }, { 117577, 2 }, { 117606, 2 }, { 117645, 2 }, { 117655, 2 }, { 117692, 2 }, { 117705, 1 }, { 117731, 1 }, { 117762, 4 }, { 117780, 2 }, { 117974, 1 }, { 118057, 1 }, { 118099, 2 }, { 118107, 2 }, + { 118113, 2 }, { 118175, 2 }, { 118198, 2 }, { 118232, 2 }, { 118326, 1 }, { 118438, 31 }, { 118469, 2 }, { 118521, 31 }, { 118565, 2 }, { 118593, 2 }, { 118602, 2 }, { 118652, 2 }, { 118668, 2 }, { 118689, 3 }, { 118703, 14 }, { 118705, 2 }, { 118813, 2 }, { 118825, 2 }, { 118894, 3 }, { 118915, 2 }, { 118962, 2 }, { 118986, 2 }, + { 119045, 2 }, { 119054, 1 }, { 119054, 1 }, { 119119, 2 }, { 119149, 2 }, { 119206, 1 }, { 119316, 2 }, { 119387, 2 }, { 119404, 3 }, { 119516, 2 }, { 119520, 2 }, { 119571, 3 }, { 119573, 2 }, { 119610, 5 }, { 119621, 2 }, { 119623, 4 }, { 119672, 2 }, { 119692, 3 }, { 119734, 2 }, { 119742, 1 }, { 119754, 1 }, { 119785, 2 }, + { 120001, 2 }, { 120115, 4 }, { 120260, 2 }, { 120314, 2 }, { 120416, 2 }, { 120435, 1 }, { 120450, 3 }, { 120530, 2 }, { 120550, 5 }, { 120730, 2 }, { 120731, 2 }, { 120751, 3 }, { 120755, 2 }, { 120869, 2 }, { 120988, 2 }, { 121061, 2 }, { 121177, 2 }, { 121212, 2 }, { 121214, 1 }, { 121286, 2 }, { 121331, 1 }, { 121344, 2 }, + { 121407, 2 }, { 121424, 1 }, { 121491, 2 }, { 121568, 1 }, { 121588, 6 }, { 121651, 2 }, { 121676, 2 }, { 121785, 4 }, { 121830, 3 }, { 121919, 1 }, { 121951, 2 }, { 121991, 1 }, { 122056, 2 }, { 122062, 2 }, { 122144, 2 }, { 122183, 1 }, { 122331, 2 }, { 122466, 2 }, { 122558, 2 }, { 122570, 2 }, { 122676, 2 }, { 122733, 2 }, + { 122774, 6 }, { 122783, 2 }, { 122825, 2 }, { 122865, 2 }, { 122884, 2 }, { 122892, 2 }, { 122911, 2 }, { 122929, 2 }, { 122936, 2 }, { 123190, 2 }, { 123271, 2 }, { 123271, 2 }, { 123302, 7 }, { 123391, 2 }, { 123394, 2 }, { 123416, 1 }, { 123708, 2 }, { 123752, 2 }, { 123761, 2 }, { 123783, 2 }, { 123794, 2 }, { 123817, 2 }, + { 123820, 1 }, { 123823, 1 }, { 123857, 3 }, { 123886, 2 }, { 124023, 1 }, { 124029, 2 }, { 124042, 2 }, { 124056, 3 }, { 124071, 6 }, { 124105, 5 }, { 124143, 2 }, { 124191, 2 }, { 124207, 1 }, { 124257, 2 }, { 124306, 3 }, { 124338, 2 }, { 124388, 8 }, { 124400, 2 }, { 124418, 2 }, { 124502, 2 }, { 124521, 1 }, { 124533, 2 }, + { 124645, 2 }, { 124685, 1 }, { 124694, 2 }, { 124700, 1 }, { 124736, 2 }, { 124891, 7 }, { 124920, 2 }, { 124983, 2 }, { 125014, 2 }, { 125038, 2 }, { 125084, 2 }, { 125162, 2 }, { 125193, 2 }, { 125285, 2 }, { 125368, 2 }, { 125409, 2 }, { 125570, 2 }, { 125601, 2 }, { 125641, 1 }, { 125721, 2 }, { 125731, 2 }, { 125803, 2 }, + { 125904, 2 }, { 125973, 2 }, { 126018, 1 }, { 126034, 5 }, { 126094, 1 }, { 126144, 1 }, { 126195, 2 }, { 126297, 2 }, { 126389, 2 }, { 126429, 2 }, { 126439, 2 }, { 126499, 2 }, { 126501, 1 }, { 126587, 2 }, { 126663, 2 }, { 126681, 2 }, { 126687, 1 }, { 126781, 2 }, { 126783, 2 }, { 126840, 8 }, { 126843, 2 }, { 126959, 2 }, + { 127015, 2 }, { 127101, 2 }, { 127149, 2 }, { 127197, 3 }, { 127268, 2 }, { 127372, 2 }, { 127385, 2 }, { 127473, 4 }, { 127539, 2 }, { 127598, 2 }, { 127613, 14 }, { 127683, 3 }, { 127684, 2 }, { 127697, 2 }, { 127698, 3 }, { 127773, 2 }, { 127781, 1 }, { 127839, 2 }, { 127905, 2 }, { 127949, 2 }, { 128035, 2 }, { 128046, 1 }, + { 128167, 2 }, { 128271, 2 }, { 128307, 1 }, { 128320, 2 }, { 128330, 2 }, { 128375, 2 }, { 128381, 4 }, { 128447, 2 }, { 128462, 2 }, { 128466, 3 }, { 128466, 2 }, { 128496, 2 }, { 128589, 2 }, { 128616, 3 }, { 128679, 1 }, { 128770, 1 }, { 128793, 2 }, { 128802, 2 }, { 128813, 2 }, { 128900, 2 }, { 128949, 2 }, { 129269, 2 }, + { 129271, 3 }, { 129278, 2 }, { 129343, 1 }, { 129408, 2 }, { 129408, 1 }, { 129421, 6 }, { 129461, 2 }, { 129469, 3 }, { 129482, 2 }, { 129502, 2 }, { 129512, 2 }, { 129551, 2 }, { 129629, 2 }, { 129632, 2 }, { 129679, 1 }, { 129725, 2 }, { 130007, 2 }, { 130018, 16 }, { 130057, 2 }, { 130071, 2 }, { 130087, 2 }, { 130188, 1 }, + { 130202, 2 }, { 130316, 2 }, { 130328, 1 }, { 130466, 2 }, { 130549, 2 }, { 130649, 2 }, { 130705, 3 }, { 130800, 2 }, { 130907, 2 }, { 130989, 2 }, { 131103, 2 }, { 131127, 2 }, { 131200, 5 }, { 131241, 6 }, { 131351, 2 }, { 131413, 2 }, { 131448, 2 }, { 131599, 2 }, { 131634, 1 }, { 131687, 2 }, { 131739, 2 }, { 131758, 2 }, + { 131765, 2 }, { 131787, 3 }, { 131819, 3 }, { 131868, 2 }, { 131886, 2 }, { 131901, 4 }, { 131977, 2 }, { 131990, 2 }, { 132035, 2 }, { 132035, 2 }, { 132043, 2 }, { 132173, 2 }, { 132181, 4 }, { 132181, 6 }, { 132194, 5 }, { 132252, 2 }, { 132262, 6 }, { 132271, 1 }, { 132285, 2 }, { 132328, 2 }, { 132335, 1 }, { 132337, 1 }, + { 132389, 5 }, { 132430, 2 }, { 132451, 2 }, { 132499, 4 }, { 132503, 1 }, { 132520, 4 }, { 132541, 4 }, { 132860, 2 }, { 132862, 4 }, { 132874, 12 }, { 132874, 13 }, { 132875, 12 }, { 132911, 2 }, { 132973, 2 }, { 133051, 2 }, { 133062, 2 }, { 133067, 2 }, { 133138, 2 }, { 133184, 2 }, { 133231, 2 }, { 133297, 3 }, { 133344, 2 }, + { 133385, 4 }, { 133408, 2 }, { 133464, 2 }, { 133522, 2 }, { 133631, 2 }, { 133631, 2 }, { 133702, 2 }, { 133705, 1 }, { 133721, 2 }, { 133746, 2 }, { 133773, 3 }, { 133819, 2 }, { 133843, 2 }, { 133929, 2 }, { 133946, 2 }, { 134113, 4 }, { 134151, 2 }, { 134289, 1 }, { 134385, 2 }, { 134429, 2 }, { 134506, 2 }, { 134511, 2 }, + { 134521, 2 }, { 134558, 1 }, { 134710, 2 }, { 134738, 2 }, { 134751, 3 }, { 134818, 2 }, { 134820, 4 }, { 134879, 2 }, { 134919, 2 }, { 134947, 2 }, { 134948, 3 }, { 135040, 3 }, { 135125, 10 }, { 135155, 2 }, { 135228, 2 }, { 135255, 2 }, { 135296, 3 }, { 135322, 2 }, { 135349, 2 }, { 135428, 3 }, { 135476, 1 }, { 135503, 2 }, + { 135524, 2 }, { 135550, 4 }, { 135594, 2 }, { 135597, 2 }, { 135624, 3 }, { 135741, 2 }, { 135753, 2 }, { 135842, 2 }, { 135853, 2 }, { 135896, 3 }, { 136004, 1 }, { 136061, 1 }, { 136068, 1 }, { 136106, 2 }, { 136145, 2 }, { 136145, 2 }, { 136173, 2 }, { 136186, 2 }, { 136196, 2 }, { 136201, 2 }, { 136211, 2 }, { 136268, 2 }, + { 136298, 2 }, { 136377, 2 }, { 136420, 2 }, { 136475, 2 }, { 136486, 1 }, { 136554, 2 }, { 136641, 2 }, { 136770, 1 }, { 136873, 2 }, { 136877, 1 }, { 136906, 2 }, { 137092, 2 }, { 137143, 2 }, { 137200, 3 }, { 137232, 2 }, { 137239, 2 }, { 137248, 2 }, { 137281, 1 }, { 137301, 2 }, { 137314, 3 }, { 137352, 1 }, { 137365, 2 }, + { 137375, 2 }, { 137411, 2 }, { 137424, 2 }, { 137516, 2 }, { 137532, 2 }, { 137593, 2 }, { 137600, 2 }, { 137658, 2 }, { 137703, 2 }, { 137766, 2 }, { 137791, 2 }, { 137801, 2 }, { 137864, 2 }, { 137870, 3 }, { 137931, 2 }, { 138009, 3 }, { 138013, 1 }, { 138013, 1 }, { 138066, 2 }, { 138073, 2 }, { 138114, 2 }, { 138150, 2 }, + { 138236, 2 }, { 138276, 2 }, { 138286, 2 }, { 138298, 3 }, { 138309, 1 }, { 138373, 3 }, { 138524, 2 }, { 138535, 1 }, { 138593, 4 }, { 138611, 1 }, { 138725, 2 }, { 138807, 2 }, { 138819, 3 }, { 138849, 5 }, { 138867, 2 }, { 138907, 2 }, { 138930, 3 }, { 139026, 2 }, { 139102, 2 }, { 139108, 3 }, { 139184, 1 }, { 139209, 3 }, + { 139282, 2 }, { 139289, 4 }, { 139382, 1 }, { 139421, 1 }, { 139436, 2 }, { 139450, 1 }, { 139523, 3 }, { 139533, 2 }, { 139590, 2 }, { 139590, 2 }, { 139722, 2 }, { 139785, 2 }, { 139785, 1 }, { 139798, 2 }, { 139813, 2 }, { 139868, 2 }, { 139935, 3 }, { 139990, 3 }, { 140050, 2 }, { 140177, 2 }, { 140177, 4 }, { 140408, 2 }, + { 140420, 3 }, { 140461, 2 }, { 140578, 15 }, { 140605, 1 }, { 140662, 1 }, { 140755, 2 }, { 140786, 2 }, { 140846, 2 }, { 140874, 2 }, { 140959, 1 }, { 140973, 2 }, { 141128, 2 }, { 141132, 2 }, { 141257, 2 }, { 141290, 1 }, { 141360, 2 }, { 141472, 2 }, { 141545, 2 }, { 141545, 2 }, { 141575, 1 }, { 141606, 5 }, { 141655, 2 }, + { 141735, 2 }, { 141767, 5 }, { 141796, 2 }, { 141841, 2 }, { 141915, 2 }, { 141923, 1 }, { 141932, 2 }, { 141994, 2 }, { 142018, 2 }, { 142029, 3 }, { 142072, 2 }, { 142128, 2 }, { 142133, 1 }, { 142261, 2 }, { 142304, 1 }, { 142400, 2 }, { 142401, 2 }, { 142409, 2 }, { 142479, 2 }, { 142522, 1 }, { 142552, 1 }, { 142589, 2 }, + { 142596, 2 }, { 142753, 1 }, { 142766, 2 }, { 142796, 2 }, { 142836, 2 }, { 142871, 2 }, { 143058, 3 }, { 143059, 6 }, { 143063, 3 }, { 143065, 2 }, { 143141, 4 }, { 143173, 2 }, { 143374, 2 }, { 143399, 2 }, { 143406, 2 }, { 143429, 3 }, { 143430, 2 }, { 143462, 1 }, { 143579, 2 }, { 143663, 2 }, { 143844, 3 }, { 143851, 2 }, + { 143926, 2 }, { 143931, 2 }, { 144051, 6 }, { 144085, 10 }, { 144147, 2 }, { 144188, 4 }, { 144238, 4 }, { 144353, 2 }, { 144465, 2 }, { 144474, 2 }, { 144637, 2 }, { 144638, 1 }, { 144648, 1 }, { 144661, 3 }, { 144812, 2 }, { 144847, 2 }, { 144901, 8 }, { 145058, 2 }, { 145122, 8 }, { 145134, 2 }, { 145150, 2 }, { 145299, 1 }, + { 145313, 2 }, { 145314, 3 }, { 145374, 2 }, { 145412, 2 }, { 145432, 2 }, { 145446, 2 }, { 145534, 3 }, { 145592, 2 }, { 145614, 2 }, { 145648, 2 }, { 145721, 2 }, { 145858, 1 }, { 145970, 3 }, { 145984, 3 }, { 146004, 2 }, { 146016, 3 }, { 146048, 2 }, { 146097, 3 }, { 146103, 2 }, { 146136, 2 }, { 146194, 3 }, { 146230, 1 }, + { 146254, 2 }, { 146261, 4 }, { 146269, 4 }, { 146393, 2 }, { 146411, 3 }, { 146501, 2 }, { 146547, 2 }, { 146547, 2 }, { 146573, 2 }, { 146616, 2 }, { 146622, 3 }, { 146728, 3 }, { 146781, 5 }, { 146805, 4 }, { 146921, 2 }, { 147002, 3 }, { 147072, 2 }, { 147159, 2 }, { 147170, 2 }, { 147203, 1 }, { 147245, 2 }, { 147278, 2 }, + { 147422, 2 }, { 147471, 2 }, { 147491, 2 }, { 147607, 4 }, { 147693, 2 }, { 147763, 2 }, { 147775, 6 }, { 147776, 4 }, { 147824, 2 }, { 147922, 2 }, { 147922, 2 }, { 147937, 2 }, { 147957, 2 }, { 147980, 2 }, { 148008, 2 }, { 148018, 2 }, { 148046, 3 }, { 148071, 4 }, { 148106, 3 }, { 148122, 2 }, { 148139, 2 }, { 148175, 2 }, + { 148318, 2 }, { 148514, 2 }, { 148528, 2 }, { 148539, 2 }, { 148545, 2 }, { 148564, 2 }, { 148569, 2 }, { 148607, 3 }, { 148712, 2 }, { 148751, 2 }, { 148792, 4 }, { 148807, 2 }, { 148818, 2 }, { 148846, 9 }, { 148848, 2 }, { 148851, 2 }, { 148861, 3 }, { 148924, 32 }, { 148934, 2 }, { 149037, 1 }, { 149127, 3 }, { 149132, 2 }, + { 149181, 1 }, { 149181, 2 }, { 149206, 2 }, { 149216, 7 }, { 149240, 4 }, { 149240, 1 }, { 149279, 1 }, { 149280, 3 }, { 149292, 2 }, { 149314, 2 }, { 149344, 2 }, { 149364, 4 }, { 149388, 2 }, { 149438, 2 }, { 149520, 2 }, { 149566, 2 }, { 149630, 2 }, { 149682, 2 }, { 149691, 1 }, { 149703, 2 }, { 149775, 2 }, { 149796, 1 }, + { 149863, 1 }, { 149884, 2 }, { 149888, 1 }, { 149983, 2 }, { 150078, 3 }, { 150083, 6 }, { 150175, 1 }, { 150235, 2 }, { 150238, 2 }, { 150298, 3 }, { 150321, 2 }, { 150382, 2 }, { 150510, 4 }, { 150574, 2 }, { 150619, 5 }, { 150645, 2 }, { 150694, 2 }, { 150732, 8 }, { 150764, 2 }, { 150813, 2 }, { 150871, 2 }, { 150879, 2 }, + { 150888, 1 }, { 150920, 2 }, { 151009, 2 }, { 151013, 2 }, { 151019, 2 }, { 151063, 2 }, { 151067, 2 }, { 151125, 1 }, { 151151, 5 }, { 151172, 2 }, { 151197, 4 }, { 151228, 70 }, { 151292, 2 }, { 151354, 2 }, { 151392, 2 }, { 151396, 1 }, { 151412, 2 }, { 151514, 2 }, { 151529, 2 }, { 151567, 2 }, { 151589, 2 }, { 151641, 2 }, + { 151672, 2 }, { 151730, 2 }, { 151748, 2 }, { 151770, 1 }, { 151788, 2 }, { 151795, 2 }, { 151805, 2 }, { 152046, 5 }, { 152048, 3 }, { 152054, 2 }, { 152057, 3 }, { 152058, 2 }, { 152075, 2 }, { 152090, 9 }, { 152205, 2 }, { 152440, 2 }, { 152480, 2 }, { 152484, 2 }, { 152601, 2 }, { 152714, 2 }, { 152801, 2 }, { 152819, 10 }, + { 152825, 2 }, { 152896, 10 }, { 152937, 2 }, { 152938, 2 }, { 152939, 3 }, { 153042, 2 }, { 153069, 2 }, { 153099, 2 }, { 153164, 2 }, { 153234, 2 }, { 153266, 2 }, { 153345, 2 }, { 153420, 2 }, { 153479, 2 }, { 153488, 4 }, { 153502, 2 }, { 153663, 3 }, { 153740, 1 }, { 153780, 2 }, { 153824, 6 }, { 153938, 2 }, { 153985, 2 }, + { 154022, 2 }, { 154022, 1 }, { 154072, 4 }, { 154109, 2 }, { 154189, 2 }, { 154222, 2 }, { 154228, 2 }, { 154265, 15 }, { 154324, 2 }, { 154350, 2 }, { 154375, 2 }, { 154396, 2 }, { 154431, 2 }, { 154463, 2 }, { 154475, 3 }, { 154510, 1 }, { 154518, 1 }, { 154529, 2 }, { 154710, 2 }, { 154742, 2 }, { 154792, 2 }, { 154871, 4 }, + { 154960, 2 }, { 154961, 2 }, { 154964, 2 }, { 154989, 3 }, { 155002, 3 }, { 155079, 2 }, { 155105, 2 }, { 155107, 2 }, { 155258, 1 }, { 155328, 2 }, { 155328, 2 }, { 155347, 2 }, { 155369, 2 }, { 155447, 2 }, { 155482, 2 }, { 155508, 2 }, { 155531, 2 }, { 155553, 1 }, { 155647, 3 }, { 155659, 9 }, { 155859, 2 }, { 155960, 2 }, + { 156009, 2 }, { 156062, 2 }, { 156143, 3 }, { 156217, 3 }, { 156252, 7 }, { 156260, 2 }, { 156274, 2 }, { 156339, 2 }, { 156354, 3 }, { 156406, 2 }, { 156550, 2 }, { 156694, 2 }, { 156730, 12 }, { 156795, 4 }, { 156806, 3 }, { 156818, 5 }, { 156835, 3 }, { 156850, 3 }, { 156861, 4 }, { 156877, 2 }, { 156915, 6 }, { 156967, 2 }, + { 157046, 2 }, { 157208, 3 }, { 157260, 2 }, { 157364, 2 }, { 157365, 1 }, { 157371, 2 }, { 157440, 2 }, { 157453, 2 }, { 157482, 1 }, { 157505, 2 }, { 157511, 4 }, { 157522, 2 }, { 157562, 2 }, { 157562, 2 }, { 157702, 2 }, { 157734, 3 }, { 157807, 2 }, { 157851, 2 }, { 157882, 2 }, { 157957, 2 }, { 158227, 2 }, { 158284, 2 }, + { 158292, 2 }, { 158310, 3 }, { 158310, 3 }, { 158330, 2 }, { 158358, 1 }, { 158493, 2 }, { 158596, 2 }, { 158736, 2 }, { 158812, 1 }, { 158830, 2 }, { 158846, 1 }, { 158884, 1 }, { 158918, 2 }, { 159003, 2 }, { 159056, 2 }, { 159189, 2 }, { 159372, 2 }, { 159373, 2 }, { 159410, 3 }, { 159421, 4 }, { 159429, 2 }, { 159505, 2 }, + { 159559, 1 }, { 159574, 2 }, { 159587, 4 }, { 159683, 2 }, { 159745, 1 }, { 159748, 3 }, { 159858, 2 }, { 159945, 1 }, { 159971, 4 }, { 159982, 2 }, { 160079, 13 }, { 160084, 2 }, { 160085, 4 }, { 160104, 9 }, { 160197, 2 }, { 160295, 2 }, { 160365, 2 }, { 160372, 1 }, { 160392, 3 }, { 160408, 1 }, { 160446, 1 }, { 160540, 2 }, + { 160599, 1 }, { 160604, 2 }, { 160745, 3 }, { 160752, 2 }, { 160794, 2 }, { 160826, 2 }, { 160846, 2 }, { 160871, 2 }, { 160957, 2 }, { 160986, 2 }, { 161053, 2 }, { 161133, 3 }, { 161133, 1 }, { 161162, 3 }, { 161247, 2 }, { 161270, 2 }, { 161292, 6 }, { 161370, 2 }, { 161420, 2 }, { 161446, 2 }, { 161487, 2 }, { 161511, 3 }, + { 161512, 1 }, { 161580, 2 }, { 161782, 12 }, { 161784, 8 }, { 161786, 2 }, { 161786, 8 }, { 161787, 7 }, { 161795, 2 }, { 161825, 7 }, { 161833, 4 }, { 161892, 2 }, { 161930, 2 }, { 161992, 2 }, { 162054, 2 }, { 162176, 2 }, { 162183, 2 }, { 162219, 2 }, { 162245, 2 }, { 162288, 2 }, { 162361, 3 }, { 162370, 2 }, { 162388, 2 }, + { 162434, 2 }, { 162447, 2 }, { 162524, 2 }, { 162542, 3 }, { 162562, 2 }, { 162594, 2 }, { 162646, 1 }, { 162662, 2 }, { 162761, 2 }, { 162780, 2 }, { 162802, 2 }, { 162820, 3 }, { 162824, 2 }, { 162908, 2 }, { 162968, 2 }, { 163171, 2 }, { 163190, 2 }, { 163288, 1 }, { 163288, 1 }, { 163397, 2 }, { 163405, 6 }, { 163414, 1 }, + { 163506, 1 }, { 163558, 2 }, { 163565, 1 }, { 163568, 3 }, { 163619, 2 }, { 163633, 2 }, { 163678, 2 }, { 163745, 3 }, { 163765, 4 }, { 163773, 2 }, { 163793, 2 }, { 163878, 7 }, { 163949, 2 }, { 163975, 2 }, { 163991, 2 }, { 164016, 2 }, { 164020, 3 }, { 164068, 1 }, { 164076, 4 }, { 164082, 3 }, { 164176, 2 }, { 164236, 2 }, + { 164238, 1 }, { 164315, 2 }, { 164449, 2 }, { 164529, 2 }, { 164574, 4 }, { 164591, 2 }, { 164595, 2 }, { 164611, 2 }, { 164623, 4 }, { 164632, 10 }, { 164691, 2 }, { 164706, 2 }, { 164755, 2 }, { 164761, 2 }, { 164973, 2 }, { 165030, 2 }, { 165090, 2 }, { 165099, 1 }, { 165126, 2 }, { 165188, 2 }, { 165205, 2 }, { 165275, 1 }, + { 165347, 2 }, { 165381, 2 }, { 165562, 2 }, { 165563, 1 }, { 165594, 2 }, { 165641, 2 }, { 165663, 6 }, { 165759, 2 }, { 165811, 2 }, { 165822, 1 }, { 165830, 1 }, { 165903, 1 }, { 165921, 2 }, { 165953, 1 }, { 166022, 1 }, { 166294, 2 }, { 166333, 2 }, { 166420, 2 }, { 166433, 2 }, { 166442, 1 }, { 166536, 2 }, { 166543, 2 }, + { 166556, 2 }, { 166571, 2 }, { 166575, 1 }, { 166588, 2 }, { 166601, 2 }, { 166663, 3 }, { 166692, 1 }, { 166710, 2 }, { 166759, 2 }, { 166785, 3 }, { 166842, 2 }, { 166843, 2 }, { 166864, 2 }, { 166902, 2 }, { 166996, 2 }, { 166999, 2 }, { 167038, 2 }, { 167112, 4 }, { 167112, 2 }, { 167177, 2 }, { 167180, 2 }, { 167229, 1 }, + { 167298, 2 }, { 167306, 4 }, { 167309, 3 }, { 167402, 2 }, { 167405, 2 }, { 167433, 2 }, { 167435, 1 }, { 167461, 3 }, { 167553, 3 }, { 167688, 5 }, { 167689, 2 }, { 167709, 2 }, { 167744, 2 }, { 167821, 2 }, { 167825, 2 }, { 167925, 10 }, { 167969, 2 }, { 168024, 2 }, { 168089, 2 }, { 168104, 2 }, { 168194, 2 }, { 168305, 2 }, + { 168316, 2 }, { 168366, 2 }, { 168423, 2 }, { 168568, 3 }, { 168582, 2 }, { 168615, 3 }, { 168618, 2 }, { 168638, 2 }, { 168671, 2 }, { 168736, 2 }, { 168747, 2 }, { 168750, 4 }, { 168808, 3 }, { 168814, 4 }, { 168820, 2 }, { 168914, 2 }, { 168968, 2 }, { 168979, 2 }, { 169006, 2 }, { 169069, 2 }, { 169106, 3 }, { 169158, 2 }, + { 169158, 2 }, { 169189, 2 }, { 169253, 2 }, { 169259, 1 }, { 169279, 1 }, { 169325, 8 }, { 169349, 2 }, { 169353, 2 }, { 169378, 2 }, { 169432, 2 }, { 169476, 1 }, { 169476, 1 }, { 169525, 2 }, { 169538, 7 }, { 169555, 2 }, { 169571, 2 }, { 169594, 4 }, { 169687, 2 }, { 169799, 2 }, { 169831, 2 }, { 170042, 2 }, { 170061, 2 }, + { 170065, 1 }, { 170128, 6 }, { 170148, 20 }, { 170215, 70 }, { 170256, 60 }, { 170266, 69 }, { 170275, 7 }, { 170277, 6 }, { 170500, 3 }, { 170516, 3 }, { 170601, 2 }, { 170666, 2 }, { 170668, 4 }, { 170668, 1 }, { 170716, 3 }, { 170728, 3 }, { 170735, 5 }, { 170847, 3 }, { 170852, 9 }, { 170858, 2 }, { 170859, 3 }, { 170956, 2 }, + { 170956, 1 }, { 170967, 2 }, { 171005, 2 }, { 171113, 2 }, { 171279, 2 }, { 171400, 2 }, { 171405, 2 }, { 171448, 1 }, { 171490, 2 }, { 171567, 32 }, { 171590, 2 }, { 171723, 2 }, { 171737, 3 }, { 171958, 2 }, { 171967, 2 } + }; + } + + /* sub-class to avoid 65k limit of a single class */ + private static class SampleDataHolder2 { + /* + * Array of [milliseconds, latency] + */ + private static int[][] data = new int[][] { { 0, 3 }, { 43, 33 }, { 45, 11 }, { 45, 1 }, { 68, 13 }, { 88, 10 }, { 158, 68 }, { 158, 4 }, { 169, 168 }, { 267, 68 }, { 342, 68 }, { 438, 68 }, { 464, 7 }, { 504, 68 }, { 541, 6 }, { 541, 68 }, { 562, 68 }, { 581, 3 }, { 636, 68 }, { 778, 68 }, { 825, 1 }, { 859, 68 }, + { 948, 1 }, { 1043, 68 }, { 1145, 68 }, { 1152, 1 }, { 1218, 5 }, { 1229, 68 }, { 1259, 68 }, { 1333, 68 }, { 1349, 68 }, { 1392, 68 }, { 1468, 1 }, { 1551, 68 }, { 1586, 68 }, { 1685, 68 }, { 1696, 1 }, { 1807, 68 }, { 1817, 3 }, { 1817, 6 }, { 1847, 68 }, { 1870, 68 }, { 1939, 68 }, { 2050, 68 }, { 2129, 3 }, { 2141, 68 }, + { 2265, 68 }, { 2414, 1 }, { 2693, 68 }, { 2703, 68 }, { 2791, 68 }, { 2838, 68 }, { 2906, 68 }, { 2981, 68 }, { 3008, 68 }, { 3026, 4 }, { 3077, 68 }, { 3273, 68 }, { 3282, 68 }, { 3286, 68 }, { 3318, 3 }, { 3335, 5 }, { 3710, 68 }, { 3711, 1 }, { 3745, 68 }, { 3748, 4 }, { 3767, 3 }, { 3809, 3 }, { 3835, 35 }, { 4083, 1 }, + { 4116, 68 }, { 4117, 1 }, { 4157, 1 }, { 4279, 68 }, { 4344, 68 }, { 4452, 68 }, { 4530, 68 }, { 4583, 68 }, { 4647, 3 }, { 4758, 68 }, { 4776, 68 }, { 4793, 68 }, { 4901, 68 }, { 4909, 68 }, { 4962, 68 }, { 4984, 68 }, { 5022, 68 }, { 5139, 68 }, { 5166, 1 }, { 5174, 68 }, { 5187, 68 }, { 5225, 68 }, { 5234, 68 }, { 5263, 1 }, + { 5325, 68 }, { 5355, 4 }, { 5407, 1 }, { 5414, 68 }, { 5589, 68 }, { 5595, 68 }, { 5747, 68 }, { 5780, 68 }, { 5788, 68 }, { 5796, 68 }, { 5818, 68 }, { 5975, 1 }, { 6018, 1 }, { 6270, 68 }, { 6272, 68 }, { 6348, 68 }, { 6372, 68 }, { 6379, 68 }, { 6439, 68 }, { 6442, 68 }, { 6460, 68 }, { 6460, 68 }, { 6509, 68 }, { 6511, 1 }, + { 6514, 4 }, { 6530, 8 }, { 6719, 68 }, { 6760, 68 }, { 6784, 68 }, { 6838, 1 }, { 6861, 68 }, { 6947, 68 }, { 7013, 68 }, { 7075, 68 }, { 7122, 5 }, { 7130, 68 }, { 7209, 3 }, { 7259, 68 }, { 7309, 1 }, { 7315, 3 }, { 7322, 68 }, { 7348, 68 }, { 7420, 68 }, { 7461, 68 }, { 7545, 68 }, { 7554, 3 }, { 7630, 68 }, { 7666, 68 }, + { 7815, 1 }, { 7972, 1 }, { 7972, 68 }, { 7988, 68 }, { 8049, 8 }, { 8254, 68 }, { 8269, 68 }, { 8352, 1 }, { 8378, 68 }, { 8526, 68 }, { 8531, 68 }, { 8583, 68 }, { 8615, 68 }, { 8619, 3 }, { 8623, 68 }, { 8692, 1 }, { 8698, 68 }, { 8773, 68 }, { 8777, 3 }, { 8822, 68 }, { 8929, 68 }, { 8935, 68 }, { 9025, 68 }, { 9054, 68 }, + { 9056, 1 }, { 9086, 68 }, { 9147, 3 }, { 9219, 68 }, { 9230, 3 }, { 9248, 68 }, { 9283, 68 }, { 9314, 68 }, { 9418, 1 }, { 9426, 68 }, { 9456, 1 }, { 9594, 68 }, { 9628, 68 }, { 9642, 68 }, { 9646, 68 }, { 9686, 1 }, { 9709, 68 }, { 9771, 3 }, { 9782, 68 }, { 9884, 68 }, { 9914, 5 }, { 10004, 4 }, { 10033, 6 }, { 10052, 68 }, + { 10086, 68 }, { 10168, 68 }, { 10176, 1 }, { 10228, 68 }, { 10312, 68 }, { 10372, 68 }, { 10622, 68 }, { 10685, 68 }, { 10687, 1 }, { 10787, 68 }, { 11010, 68 }, { 11024, 68 }, { 11044, 68 }, { 11086, 68 }, { 11149, 1 }, { 11198, 68 }, { 11265, 68 }, { 11302, 68 }, { 11326, 68 }, { 11354, 68 }, { 11404, 1 }, { 11473, 68 }, + { 11506, 68 }, { 11548, 4 }, { 11575, 68 }, { 11621, 4 }, { 11625, 3 }, { 11625, 1 }, { 11642, 4 }, { 11859, 5 }, { 11870, 68 }, { 11872, 3 }, { 11880, 7 }, { 11886, 3 }, { 11905, 6 }, { 11880, 3 }, { 11912, 6 }, { 11916, 4 }, { 11916, 3 }, { 11965, 4 }, { 12068, 13 }, { 12106, 68 }, { 12120, 68 }, { 12221, 68 }, { 12257, 68 }, + { 12361, 68 }, { 12411, 68 }, { 12473, 3 }, { 12554, 68 }, { 12583, 68 }, { 12654, 68 }, { 12665, 68 }, { 12744, 1 }, { 12775, 68 }, { 12858, 68 }, { 12993, 68 }, { 13007, 3 }, { 13025, 4 }, { 13038, 68 }, { 13092, 4 }, { 13094, 5 }, { 13095, 1 }, { 13110, 68 }, { 13116, 1 }, { 13140, 68 }, { 13169, 1 }, { 13186, 68 }, + { 13202, 68 }, { 13202, 1 }, { 13256, 68 }, { 13344, 68 }, { 13373, 68 }, { 13396, 3 }, { 13446, 68 }, { 13451, 3 }, { 13475, 68 }, { 13521, 1 }, { 13587, 68 }, { 13592, 68 }, { 13708, 3 }, { 13711, 1 }, { 13741, 1 }, { 13757, 1 }, { 13847, 68 }, { 13881, 3 }, { 13915, 1 }, { 14005, 68 }, { 14028, 68 }, { 14037, 68 }, + { 14074, 68 }, { 14135, 68 }, { 14176, 68 }, { 14227, 68 }, { 14228, 68 }, { 14271, 3 }, { 14279, 3 }, { 14493, 68 }, { 14535, 3 }, { 14535, 1 }, { 14680, 68 }, { 14717, 68 }, { 14725, 1 }, { 14790, 68 }, { 14801, 1 }, { 14959, 68 }, { 15052, 68 }, { 15055, 1 }, { 15055, 1 }, { 15075, 68 }, { 15103, 8 }, { 15153, 16 }, + { 15191, 68 }, { 15240, 68 }, { 15313, 68 }, { 15323, 68 }, { 15341, 1 }, { 15383, 68 }, { 15387, 68 }, { 15491, 68 }, { 15534, 68 }, { 15539, 68 }, { 15549, 68 }, { 15554, 1 }, { 15664, 1 }, { 15726, 68 }, { 15807, 68 }, { 15842, 68 }, { 15897, 68 }, { 15913, 3 }, { 15925, 68 }, { 15935, 68 }, { 16131, 1 }, { 16211, 3 }, + { 16249, 68 }, { 16268, 68 }, { 16307, 68 }, { 16398, 68 }, { 16498, 68 }, { 16518, 1 }, { 16552, 1 }, { 16571, 68 }, { 16592, 68 }, { 16601, 3 }, { 16638, 68 }, { 16698, 68 }, { 16712, 1 }, { 16767, 68 }, { 16789, 68 }, { 16992, 68 }, { 17015, 68 }, { 17035, 68 }, { 17074, 3 }, { 17086, 3 }, { 17086, 1 }, { 17092, 1 }, + { 17110, 4 }, { 17116, 3 }, { 17236, 68 }, { 17291, 68 }, { 17291, 68 }, { 17340, 68 }, { 17342, 1 }, { 17360, 3 }, { 17436, 3 }, { 17457, 68 }, { 17508, 1 }, { 17556, 68 }, { 17601, 68 }, { 17639, 68 }, { 17671, 68 }, { 17743, 68 }, { 17857, 68 }, { 17915, 68 }, { 17992, 68 }, { 18077, 1 }, { 18088, 68 }, { 18158, 1 }, + { 18239, 16 }, { 18242, 68 }, { 18252, 3 }, { 18299, 1 }, { 18405, 68 }, { 18433, 68 }, { 18444, 68 }, { 18490, 68 }, { 18497, 68 }, { 18516, 68 }, { 18540, 68 }, { 18598, 68 }, { 18649, 68 }, { 18658, 68 }, { 18683, 68 }, { 18728, 68 }, { 18767, 1 }, { 18821, 68 }, { 18868, 68 }, { 18876, 68 }, { 18914, 14 }, { 19212, 1 }, + { 19215, 1 }, { 19293, 68 }, { 19303, 68 }, { 19336, 68 }, { 19376, 68 }, { 19419, 68 }, { 19558, 68 }, { 19559, 1 }, { 19609, 68 }, { 19688, 68 }, { 19724, 68 }, { 19820, 1 }, { 19851, 68 }, { 19881, 68 }, { 19966, 68 }, { 19983, 3 }, { 19988, 4 }, { 20047, 1 }, { 20062, 68 }, { 20091, 1 }, { 20152, 1 }, { 20183, 1 }, + { 20208, 68 }, { 20346, 68 }, { 20386, 1 }, { 20459, 68 }, { 20505, 68 }, { 20520, 1 }, { 20560, 3 }, { 20566, 3 }, { 20566, 1 }, { 20610, 68 }, { 20652, 68 }, { 20694, 68 }, { 20740, 68 }, { 20756, 68 }, { 20825, 3 }, { 20895, 68 }, { 20959, 1 }, { 20995, 68 }, { 21017, 3 }, { 21039, 68 }, { 21086, 1 }, { 21109, 3 }, { 21139, 3 }, + { 21206, 68 }, { 21230, 68 }, { 21251, 3 }, { 21352, 68 }, { 21353, 68 }, { 21370, 3 }, { 21389, 1 }, { 21445, 3 }, { 21475, 68 }, { 21528, 68 }, { 21559, 3 }, { 21604, 68 }, { 21606, 1 }, { 21815, 68 }, { 21858, 3 }, { 21860, 3 }, { 22015, 68 }, { 22065, 68 }, { 22098, 5 }, { 22105, 68 }, { 22158, 3 }, { 22197, 68 }, { 22254, 1 }, + { 22353, 68 }, { 22404, 4 }, { 22422, 68 }, { 22569, 68 }, { 22634, 68 }, { 22639, 68 }, { 22861, 68 }, { 22868, 68 }, { 22876, 1 }, { 22902, 68 }, { 22925, 68 }, { 23080, 68 }, { 23085, 3 }, { 23089, 5 }, { 23329, 1 }, { 23349, 68 }, { 23559, 5 }, { 23567, 3 }, { 23574, 68 }, { 23584, 3 }, { 23615, 3 }, { 23633, 68 }, + { 23674, 68 }, { 23678, 1 }, { 23853, 68 }, { 23875, 68 }, { 24010, 4 }, { 24076, 68 }, { 24128, 6 }, { 24248, 68 }, { 24253, 68 }, { 24259, 1 }, { 24319, 68 }, { 24319, 1 }, { 24502, 3 }, { 24666, 68 }, { 24781, 3 }, { 24792, 68 }, { 24909, 68 }, { 24993, 68 }, { 25039, 1 }, { 25090, 3 }, { 25137, 1 }, { 25138, 3 }, { 25140, 3 }, + { 25155, 5 }, { 25411, 68 }, { 25460, 68 }, { 25564, 3 }, { 25586, 3 }, { 25630, 68 }, { 25765, 68 }, { 25789, 3 }, { 25803, 68 }, { 25851, 68 }, { 25872, 68 }, { 25887, 68 }, { 25981, 1 }, { 26016, 68 }, { 26019, 1 }, { 26029, 1 }, { 26104, 7 }, { 26144, 68 }, { 26275, 1 }, { 26295, 68 }, { 26298, 1 }, { 26322, 68 }, + { 26380, 68 }, { 26408, 4 }, { 26446, 1 }, { 26553, 1 }, { 26576, 1 }, { 26635, 1 }, { 26668, 68 }, { 26675, 68 }, { 26698, 4 }, { 26748, 9 }, { 26788, 68 }, { 26932, 68 }, { 26962, 68 }, { 27042, 68 }, { 27060, 68 }, { 27163, 3 }, { 27202, 68 }, { 27290, 68 }, { 27337, 3 }, { 27376, 68 }, { 27439, 68 }, { 27458, 4 }, + { 27515, 68 }, { 27518, 1 }, { 27541, 68 }, { 27585, 3 }, { 27633, 68 }, { 27695, 68 }, { 27702, 68 }, { 27861, 68 }, { 27924, 1 }, { 28025, 14 }, { 28058, 68 }, { 28143, 68 }, { 28215, 68 }, { 28240, 68 }, { 28241, 68 }, { 28285, 68 }, { 28324, 3 }, { 28378, 68 }, { 28514, 68 }, { 28529, 68 }, { 28538, 68 }, { 28565, 3 }, + { 28697, 68 }, { 28735, 68 }, { 28769, 68 }, { 28770, 4 }, { 28788, 4 }, { 28807, 3 }, { 28807, 4 }, { 28829, 1 }, { 28853, 68 }, { 28856, 7 }, { 28864, 68 }, { 28865, 3 }, { 28915, 68 }, { 28928, 68 }, { 28964, 68 }, { 28988, 1 }, { 29031, 68 }, { 29095, 68 }, { 29189, 68 }, { 29205, 1 }, { 29230, 1 }, { 29332, 68 }, + { 29339, 68 }, { 29349, 5 }, { 29449, 68 }, { 29471, 68 }, { 29578, 68 }, { 29859, 68 }, { 29878, 68 }, { 29947, 10 }, { 30083, 68 }, { 30121, 68 }, { 30128, 68 }, { 30155, 4 }, { 30157, 1 }, { 30272, 68 }, { 30281, 68 }, { 30286, 68 }, { 30305, 68 }, { 30408, 68 }, { 30444, 268 }, { 30612, 68 }, { 30628, 68 }, { 30747, 68 }, + { 30783, 68 }, { 30808, 5 }, { 30868, 3 }, { 30875, 68 }, { 30997, 68 }, { 31000, 68 }, { 31022, 3 }, { 31111, 1 }, { 31144, 68 }, { 31146, 3 }, { 31187, 68 }, { 31324, 68 }, { 31343, 68 }, { 31416, 68 }, { 31485, 68 }, { 31539, 68 }, { 31638, 68 }, { 31648, 68 }, { 31750, 68 }, { 31754, 68 }, { 31785, 10 }, { 31786, 5 }, + { 31800, 68 }, { 31801, 4 }, { 31807, 7 }, { 31807, 3 }, { 31807, 10 }, { 31808, 3 }, { 31808, 4 }, { 31818, 6 }, { 31825, 7 }, { 31838, 68 }, { 31911, 1 }, { 31974, 68 }, { 32010, 3 }, { 32031, 68 }, { 32040, 68 }, { 32063, 1 }, { 32078, 68 }, { 32156, 68 }, { 32198, 31 }, { 32257, 68 }, { 32257, 68 }, { 32265, 68 }, + { 32330, 68 }, { 32369, 8 }, { 32404, 3 }, { 32425, 68 }, { 32432, 68 }, { 32505, 68 }, { 32531, 68 }, { 32536, 68 }, { 32549, 68 }, { 32582, 3 }, { 32590, 4 }, { 32624, 68 }, { 32644, 68 }, { 32692, 68 }, { 32695, 4 }, { 32699, 3 }, { 32726, 4 }, { 32784, 68 }, { 32832, 68 }, { 32883, 6 }, { 32965, 4 }, { 33044, 68 }, + { 33104, 68 }, { 33184, 68 }, { 33264, 1 }, { 33292, 68 }, { 33312, 1 }, { 33468, 68 }, { 33471, 1 }, { 33565, 68 }, { 33627, 68 }, { 33659, 68 }, { 33709, 68 }, { 33766, 5 }, { 33836, 68 }, { 33875, 68 }, { 33954, 68 }, { 33959, 68 }, { 34050, 68 }, { 34090, 68 }, { 34168, 68 }, { 34233, 68 }, { 34461, 68 }, { 34462, 1 }, + { 34463, 68 }, { 34472, 4 }, { 34500, 68 }, { 34520, 68 }, { 34544, 68 }, { 34614, 68 }, { 34662, 1 }, { 34676, 68 }, { 34729, 4 }, { 34803, 68 }, { 34845, 68 }, { 34913, 68 }, { 34963, 6 }, { 35019, 68 }, { 35022, 68 }, { 35070, 68 }, { 35120, 68 }, { 35132, 68 }, { 35144, 68 }, { 35205, 68 }, { 35230, 3 }, { 35244, 68 }, + { 35271, 4 }, { 35276, 68 }, { 35282, 68 }, { 35324, 3 }, { 35366, 3 }, { 35659, 68 }, { 35680, 68 }, { 35744, 68 }, { 35758, 3 }, { 35796, 68 }, { 35830, 68 }, { 35841, 7 }, { 35843, 68 }, { 35856, 68 }, { 35914, 4 }, { 35929, 13 }, { 35993, 68 }, { 35997, 1 }, { 36046, 4 }, { 36046, 1 }, { 36051, 1 }, { 36111, 68 }, { 36208, 1 }, + { 36208, 1 }, { 36306, 68 }, { 36325, 68 }, { 36386, 68 }, { 36405, 68 }, { 36443, 1 }, { 36455, 1 }, { 36538, 68 }, { 36562, 68 }, { 36566, 68 }, { 36628, 68 }, { 36693, 68 }, { 36713, 68 }, { 36730, 68 }, { 36747, 68 }, { 36786, 68 }, { 36810, 1 }, { 36848, 68 }, { 36914, 1 }, { 36920, 68 }, { 36952, 68 }, { 37071, 68 }, + { 37086, 1 }, { 37094, 3 }, { 37158, 3 }, { 37231, 68 }, { 37241, 68 }, { 37285, 68 }, { 37349, 68 }, { 37404, 68 }, { 37410, 1 }, { 37433, 4 }, { 37615, 68 }, { 37659, 68 }, { 37742, 68 }, { 37773, 68 }, { 37867, 1 }, { 37890, 68 }, { 37960, 68 }, { 38042, 3 }, { 38241, 68 }, { 38400, 68 }, { 38461, 1 }, { 38551, 68 }, + { 38611, 1 }, { 38657, 68 }, { 38729, 68 }, { 38748, 68 }, { 38815, 68 }, { 38852, 68 }, { 38890, 1 }, { 38954, 68 }, { 39119, 68 }, { 39162, 68 }, { 39175, 3 }, { 39176, 68 }, { 39231, 68 }, { 39261, 68 }, { 39467, 68 }, { 39500, 68 }, { 39507, 68 }, { 39566, 68 }, { 39608, 68 }, { 39686, 6 }, { 39730, 68 }, { 39842, 1 }, + { 39853, 1 }, { 39905, 68 }, { 39931, 68 }, { 39989, 68 }, { 40030, 68 }, { 40227, 68 }, { 40268, 68 }, { 40372, 68 }, { 40415, 1 }, { 40488, 3 }, { 40536, 68 }, { 40676, 3 }, { 40677, 68 }, { 40755, 68 }, { 40842, 68 }, { 40849, 1 }, { 40870, 3 }, { 40873, 3 }, { 40972, 68 }, { 41033, 68 }, { 41190, 68 }, { 41273, 5 }, + { 41273, 1 }, { 41293, 68 }, { 41367, 368 }, { 41376, 68 }, { 41420, 68 }, { 41473, 68 }, { 41473, 68 }, { 41493, 4 }, { 41521, 68 }, { 41533, 68 }, { 41554, 68 }, { 41568, 68 }, { 41583, 3 }, { 41728, 68 }, { 41786, 68 }, { 41836, 1 }, { 41875, 68 }, { 41933, 68 }, { 42044, 68 }, { 42075, 68 }, { 42076, 68 }, { 42133, 68 }, + { 42259, 29 }, { 42269, 3 }, { 42294, 68 }, { 42420, 68 }, { 42524, 68 }, { 42524, 1 }, { 42546, 1 }, { 42631, 68 }, { 42693, 68 }, { 42740, 68 }, { 42744, 4 }, { 42755, 1 }, { 42870, 68 }, { 42894, 68 }, { 42939, 68 }, { 42973, 68 }, { 43016, 68 }, { 43070, 68 }, { 43105, 68 }, { 43115, 68 }, { 43375, 3 }, { 43387, 1 }, + { 43424, 3 }, { 43448, 68 }, { 43480, 68 }, { 43498, 68 }, { 43651, 68 }, { 43727, 68 }, { 43879, 68 }, { 43910, 1 }, { 43977, 68 }, { 44003, 68 }, { 44080, 68 }, { 44082, 1 }, { 44136, 68 }, { 44169, 29 }, { 44186, 68 }, { 44339, 68 }, { 44350, 1 }, { 44356, 1 }, { 44430, 68 }, { 44440, 1 }, { 44530, 1 }, { 44538, 68 }, + { 44572, 68 }, { 44585, 68 }, { 44709, 68 }, { 44748, 68 }, { 44748, 68 }, { 44769, 68 }, { 44813, 68 }, { 44890, 68 }, { 45015, 68 }, { 45046, 4 }, { 45052, 68 }, { 45062, 68 }, { 45094, 6 }, { 45184, 68 }, { 45191, 68 }, { 45201, 3 }, { 45216, 3 }, { 45227, 68 }, { 45269, 1 }, { 45294, 68 }, { 45314, 68 }, { 45345, 8 }, + { 45352, 68 }, { 45365, 3 }, { 45378, 1 }, { 45392, 4 }, { 45405, 3 }, { 45410, 68 }, { 45448, 14 }, { 45450, 68 }, { 45457, 68 }, { 45466, 3 }, { 45481, 4 }, { 45486, 7 }, { 45533, 5 }, { 45576, 68 }, { 45649, 68 }, { 45917, 68 }, { 45919, 6 }, { 45919, 1 }, { 45930, 15 }, { 45930, 68 }, { 46001, 5 }, { 46036, 68 }, { 46054, 68 }, + { 46075, 68 }, { 46153, 68 }, { 46155, 68 }, { 46228, 68 }, { 46234, 68 }, { 46273, 68 }, { 46387, 68 }, { 46398, 68 }, { 46517, 68 }, { 46559, 68 }, { 46565, 1 }, { 46598, 68 }, { 46686, 68 }, { 46744, 68 }, { 46816, 3 }, { 46835, 68 }, { 46921, 68 }, { 46938, 68 }, { 46991, 68 }, { 47038, 68 }, { 47098, 3 }, { 47107, 68 }, + { 47201, 3 }, { 47327, 1 }, { 47327, 1 }, { 47338, 68 }, { 47395, 1 }, { 47499, 68 }, { 47504, 68 }, { 47515, 1 }, { 47516, 1 }, { 47600, 1 }, { 47604, 1 }, { 47707, 1 }, { 47728, 1 }, { 47748, 68 }, { 47763, 68 }, { 47807, 4 }, { 47814, 68 }, { 47822, 68 }, { 47834, 68 }, { 47843, 3 }, { 47886, 68 }, { 47893, 68 }, { 48066, 68 }, + { 48126, 68 }, { 48133, 1 }, { 48166, 68 }, { 48299, 1 }, { 48455, 68 }, { 48468, 68 }, { 48568, 68 }, { 48606, 68 }, { 48642, 68 }, { 48698, 68 }, { 48714, 68 }, { 48754, 68 }, { 48765, 3 }, { 48773, 5 }, { 48819, 68 }, { 48833, 68 }, { 48904, 68 }, { 49000, 1 }, { 49113, 168 }, { 49140, 68 }, { 49276, 68 }, { 49353, 68 }, + { 49411, 3 }, { 49418, 68 }, { 49540, 68 }, { 49544, 68 }, { 49584, 68 }, { 49602, 68 }, { 49784, 5 }, { 49822, 4 }, { 49822, 5 }, { 49828, 68 }, { 49866, 68 }, { 49922, 3 }, { 49959, 68 }, { 50045, 68 }, { 50134, 3 }, { 50140, 68 }, { 50237, 68 }, { 50247, 68 }, { 50266, 13 }, { 50290, 68 }, { 50312, 4 }, { 50314, 1 }, + { 50527, 68 }, { 50605, 1 }, { 50730, 68 }, { 50751, 68 }, { 50770, 68 }, { 50858, 68 }, { 50859, 68 }, { 50909, 68 }, { 50948, 3 }, { 51043, 68 }, { 51048, 68 }, { 51089, 68 }, { 51090, 68 }, { 51141, 68 }, { 51163, 68 }, { 51250, 68 }, { 51347, 68 }, { 51475, 68 }, { 51536, 68 }, { 51544, 68 }, { 51595, 68 }, { 51602, 19 }, + { 51643, 5 }, { 51702, 68 }, { 51702, 10 }, { 51764, 68 }, { 51793, 5 }, { 51812, 68 }, { 51839, 1 }, { 51938, 3 }, { 51941, 1 }, { 51967, 4 }, { 52049, 3 }, { 52074, 3 }, { 52098, 68 }, { 52118, 68 }, { 52119, 3 }, { 52227, 11 }, { 52246, 3 }, { 52282, 68 }, { 52451, 68 }, { 52583, 68 }, { 52601, 1 }, { 52605, 68 }, { 52615, 68 }, + { 52668, 68 }, { 52824, 68 }, { 53076, 1 }, { 53120, 1 }, { 53179, 68 }, { 53189, 68 }, { 53193, 1 }, { 53195, 68 }, { 53246, 68 }, { 53249, 68 }, { 53268, 1 }, { 53295, 68 }, { 53312, 68 }, { 53410, 68 }, { 53451, 68 }, { 53570, 68 }, { 53593, 68 }, { 53635, 68 }, { 53657, 68 }, { 53682, 3 }, { 53728, 5 }, { 53733, 68 }, + { 53753, 68 }, { 53787, 4 }, { 53807, 1 }, { 54008, 68 }, { 54059, 68 }, { 54060, 1 }, { 54080, 68 }, { 54090, 1 }, { 54138, 68 }, { 54149, 68 }, { 54168, 1 }, { 54171, 68 }, { 54216, 268 }, { 54233, 6 }, { 54434, 68 }, { 54534, 68 }, { 54562, 68 }, { 54763, 68 }, { 54791, 68 }, { 54816, 68 }, { 54909, 68 }, { 54916, 3 }, + { 54963, 68 }, { 54985, 68 }, { 54991, 3 }, { 55016, 3 }, { 55025, 3 }, { 55032, 68 }, { 55099, 68 }, { 55260, 68 }, { 55261, 68 }, { 55270, 3 }, { 55384, 68 }, { 55455, 68 }, { 55456, 68 }, { 55504, 3 }, { 55510, 68 }, { 55558, 68 }, { 55568, 68 }, { 55585, 68 }, { 55677, 68 }, { 55703, 68 }, { 55749, 68 }, { 55779, 68 }, + { 55789, 3 }, { 55792, 68 }, { 55830, 4 }, { 55835, 68 }, { 55879, 68 }, { 56076, 68 }, { 56118, 68 }, { 56314, 68 }, { 56392, 1 }, { 56411, 68 }, { 56459, 68 }, { 56553, 34 }, { 56575, 68 }, { 56733, 68 }, { 56762, 68 }, { 56793, 3 }, { 56877, 3 }, { 56927, 68 }, { 56981, 68 }, { 57014, 1 }, { 57149, 68 }, { 57162, 68 }, + { 57186, 68 }, { 57254, 68 }, { 57267, 1 }, { 57324, 68 }, { 57327, 68 }, { 57365, 4 }, { 57371, 68 }, { 57445, 68 }, { 57477, 68 }, { 57497, 68 }, { 57536, 68 }, { 57609, 68 }, { 57626, 68 }, { 57666, 68 }, { 57694, 68 }, { 57694, 68 }, { 57749, 68 }, { 57781, 7 }, { 57878, 68 }, { 57953, 68 }, { 58051, 68 }, { 58088, 68 }, + { 58097, 68 }, { 58142, 3 }, { 58142, 1 }, { 58197, 1 }, { 58221, 68 }, { 58222, 68 }, { 58244, 68 }, { 58290, 1 }, { 58296, 1 }, { 58325, 68 }, { 58378, 1 }, { 58389, 3 }, { 58430, 68 }, { 58454, 68 }, { 58551, 29 }, { 58563, 6 }, { 58681, 68 }, { 58751, 8 }, { 58752, 43 }, { 58790, 5 }, { 58846, 68 }, { 58879, 6 }, { 58953, 68 }, + { 58998, 68 }, { 59010, 1 }, { 59038, 5 }, { 59135, 68 }, { 59166, 68 }, { 59180, 68 }, { 59222, 68 }, { 59227, 68 }, { 59307, 68 }, { 59398, 3 }, { 59411, 68 }, { 59436, 3 }, { 59464, 68 }, { 59569, 68 }, { 59587, 68 }, { 59624, 3 }, { 59786, 68 }, { 59834, 68 }, { 59841, 68 }, { 59841, 1 }, { 59984, 68 }, { 59985, 68 }, + { 60003, 3 }, { 60045, 68 }, { 60097, 68 }, { 60148, 68 }, { 60172, 68 }, { 60203, 5 }, { 60565, 68 }, { 60625, 68 }, { 60743, 68 }, { 60781, 68 }, { 60892, 68 }, { 60977, 68 }, { 60979, 68 }, { 61021, 5 }, { 61021, 4 }, { 61026, 68 }, { 61139, 68 }, { 61165, 3 }, { 61204, 68 }, { 61207, 1 }, { 61248, 3 }, { 61257, 68 }, + { 61264, 6 }, { 61272, 3 }, { 61410, 68 }, { 61410, 3 }, { 61416, 68 }, { 61423, 1 }, { 61503, 68 }, { 61503, 68 }, { 61533, 68 }, { 61567, 68 }, { 61575, 68 }, { 61835, 1 }, { 61842, 1 }, { 61924, 68 }, { 61951, 6 }, { 61975, 68 }, { 61986, 3 }, { 62024, 1 }, { 62110, 68 }, { 62135, 68 }, { 62192, 68 }, { 62208, 68 }, + { 62399, 68 }, { 62400, 1 }, { 62414, 68 }, { 62423, 3 }, { 62456, 3 }, { 62459, 3 }, { 62478, 3 }, { 62484, 68 }, { 62510, 6 }, { 62511, 3 }, { 62565, 3 }, { 62610, 68 }, { 62875, 4 }, { 62896, 5 }, { 62898, 68 }, { 62904, 68 }, { 62938, 3 }, { 62943, 68 }, { 62977, 68 }, { 62989, 3 }, { 62998, 5 }, { 63069, 1 }, { 63093, 5 }, + { 63107, 68 }, { 63113, 1 }, { 63231, 4 }, { 63253, 68 }, { 63286, 4 }, { 63289, 68 }, { 63334, 1 }, { 63334, 4 }, { 63413, 68 }, { 63425, 68 }, { 63512, 10 }, { 63537, 1 }, { 63694, 1 }, { 63721, 4 }, { 63749, 68 }, { 63783, 17 }, { 63791, 3 }, { 63792, 68 }, { 63882, 25 }, { 63896, 1 }, { 63936, 68 }, { 63969, 3 }, { 63986, 68 }, + { 63988, 68 }, { 64009, 10 }, { 64018, 68 }, { 64032, 6 }, { 64125, 68 }, { 64195, 1 }, { 64221, 7 }, { 64390, 68 }, { 64459, 68 }, { 64568, 68 }, { 64784, 1 }, { 64789, 68 }, { 64829, 68 }, { 64848, 1 }, { 64914, 68 }, { 64928, 1 }, { 64939, 68 }, { 65026, 68 }, { 65057, 68 }, { 65070, 68 }, { 65193, 4 }, { 65235, 3 }, + { 65242, 68 }, { 65281, 68 }, { 65320, 68 }, { 65365, 1 }, { 65414, 68 }, { 65445, 68 }, { 65581, 68 }, { 65624, 1 }, { 65719, 68 }, { 65766, 68 }, { 65927, 68 }, { 66004, 1 }, { 66031, 68 }, { 66085, 1 }, { 66085, 68 }, { 66133, 68 }, { 66134, 68 }, { 66188, 1 }, { 66240, 68 }, { 66249, 68 }, { 66250, 68 }, { 66295, 68 }, + { 66342, 1 }, { 66352, 3 }, { 66388, 3 }, { 66432, 68 }, { 66437, 47 }, { 66497, 68 }, { 66517, 68 }, { 66526, 68 }, { 66546, 9 }, { 66605, 68 }, { 66753, 68 }, { 66792, 68 }, { 66796, 68 }, { 66828, 68 }, { 66899, 3 }, { 66970, 6 }, { 66981, 68 }, { 66983, 1 }, { 67009, 68 }, { 67017, 4 }, { 67115, 68 }, { 67117, 1 }, + { 67130, 6 }, { 67132, 7 }, { 67162, 68 }, { 67179, 6 }, { 67236, 68 }, { 67263, 3 }, { 67274, 68 }, { 67274, 68 }, { 67349, 3 }, { 67486, 68 }, { 67503, 3 }, { 67517, 1 }, { 67559, 1 }, { 67660, 68 }, { 67727, 68 }, { 67901, 68 }, { 67943, 4 }, { 67950, 68 }, { 67965, 3 }, { 68029, 68 }, { 68048, 68 }, { 68169, 68 }, { 68172, 1 }, + { 68258, 68 }, { 68288, 1 }, { 68359, 68 }, { 68441, 68 }, { 68484, 68 }, { 68488, 68 }, { 68525, 68 }, { 68535, 68 }, { 68575, 7 }, { 68575, 5 }, { 68583, 68 }, { 68588, 4 }, { 68593, 1 }, { 68597, 68 }, { 68636, 68 }, { 68636, 68 }, { 68667, 68 }, { 68785, 1 }, { 68914, 4 }, { 68915, 5 }, { 68940, 3 }, { 69010, 68 }, + { 69063, 68 }, { 69076, 68 }, { 69235, 68 }, { 69270, 68 }, { 69298, 1 }, { 69350, 5 }, { 69432, 68 }, { 69514, 68 }, { 69562, 3 }, { 69562, 4 }, { 69638, 1 }, { 69656, 68 }, { 69709, 68 }, { 69775, 68 }, { 69788, 68 }, { 70193, 68 }, { 70233, 68 }, { 70252, 68 }, { 70259, 68 }, { 70293, 3 }, { 70405, 3 }, { 70462, 68 }, + { 70515, 3 }, { 70518, 68 }, { 70535, 6 }, { 70547, 6 }, { 70577, 6 }, { 70631, 17 }, { 70667, 68 }, { 70680, 1 }, { 70694, 1 }, { 70898, 68 }, { 70916, 1 }, { 70936, 3 }, { 71033, 68 }, { 71126, 68 }, { 71158, 68 }, { 71162, 68 }, { 71421, 1 }, { 71441, 68 }, { 71557, 68 }, { 71789, 1 }, { 71816, 68 }, { 71850, 1 }, { 71869, 1 }, + { 71961, 68 }, { 71973, 4 }, { 72064, 68 }, { 72110, 68 }, { 72117, 3 }, { 72164, 68 }, { 72266, 68 }, { 72325, 68 }, { 72326, 1 }, { 72420, 68 }, { 72693, 68 }, { 72705, 1 }, { 72730, 68 }, { 72793, 68 }, { 72795, 1 }, { 72939, 1 }, { 72945, 3 }, { 72945, 68 }, { 73120, 1 }, { 73121, 5 }, { 73122, 4 }, { 73126, 1 }, { 73126, 1 }, + { 73196, 3 }, { 73219, 68 }, { 73241, 6 }, { 73272, 3 }, { 73354, 1 }, { 73368, 68 }, { 73467, 1 }, { 73517, 68 }, { 73554, 68 }, { 73678, 68 }, { 73838, 1 }, { 73881, 68 }, { 73958, 68 }, { 73985, 15 }, { 74092, 68 }, { 74205, 68 }, { 74245, 68 }, { 74277, 68 }, { 74286, 68 }, { 74353, 68 }, { 74403, 68 }, { 74428, 1 }, + { 74468, 68 }, { 74481, 3 }, { 74511, 68 }, { 74537, 68 }, { 74596, 68 }, { 74750, 68 }, { 74754, 68 }, { 74861, 68 }, { 74933, 4 }, { 74970, 1 }, { 75003, 3 }, { 75077, 1 }, { 75159, 68 }, { 75170, 68 }, { 75234, 45 }, { 75300, 3 }, { 75337, 68 }, { 75345, 68 }, { 75419, 1 }, { 75429, 68 }, { 75477, 1 }, { 75513, 68 }, + { 75536, 68 }, { 75536, 68 }, { 75539, 1 }, { 75551, 68 }, { 75561, 68 }, { 75565, 68 }, { 75590, 68 }, { 75623, 5 }, { 75773, 6 }, { 75777, 6 }, { 75785, 68 }, { 75791, 68 }, { 75804, 68 }, { 75862, 68 }, { 75924, 3 }, { 75927, 68 }, { 75996, 11 }, { 76000, 1 }, { 76006, 68 }, { 76020, 3 }, { 76110, 68 }, { 76126, 3 }, + { 76131, 68 }, { 76136, 68 }, { 76144, 68 }, { 76203, 68 }, { 76229, 3 }, { 76244, 15 }, { 76246, 68 }, { 76300, 1 }, { 76403, 3 }, { 76545, 68 }, { 76569, 68 }, { 76813, 68 }, { 76821, 68 }, { 76837, 68 }, { 76863, 68 }, { 77027, 68 }, { 77037, 65 }, { 77074, 3 }, { 77170, 68 }, { 77191, 68 }, { 77220, 68 }, { 77230, 68 }, + { 77261, 68 }, { 77277, 68 }, { 77309, 68 }, { 77314, 68 }, { 77412, 68 }, { 77419, 68 }, { 77457, 68 }, { 77633, 3 }, { 77714, 68 }, { 77855, 68 }, { 77857, 1 }, { 77876, 68 }, { 77895, 68 }, { 77916, 5 }, { 77947, 68 }, { 77948, 1 }, { 77966, 1 }, { 77996, 68 }, { 78025, 1 }, { 78064, 68 }, { 78100, 68 }, { 78113, 1 }, + { 78114, 3 }, { 78167, 68 }, { 78175, 68 }, { 78260, 68 }, { 78261, 1 }, { 78265, 68 }, { 78286, 1 }, { 78300, 68 }, { 78327, 3 }, { 78363, 1 }, { 78384, 68 }, { 78459, 68 }, { 78516, 68 }, { 78612, 68 }, { 78643, 68 }, { 78655, 68 }, { 78698, 1 }, { 78720, 3 }, { 78789, 76 }, { 78838, 5 }, { 78893, 1 }, { 78954, 7 }, + { 79007, 68 }, { 79132, 3 }, { 79193, 68 }, { 79193, 68 }, { 79226, 68 }, { 79411, 68 }, { 79422, 1 }, { 79502, 68 }, { 79593, 68 }, { 79622, 68 }, { 79657, 3 }, { 79771, 68 }, { 79866, 68 }, { 79909, 68 }, { 80005, 68 }, { 80032, 68 }, { 80060, 1 }, { 80132, 68 }, { 80149, 3 }, { 80251, 68 }, { 80363, 68 }, { 80379, 1 }, + { 80464, 68 }, { 80498, 68 }, { 80553, 68 }, { 80556, 3 }, { 80559, 1 }, { 80571, 68 }, { 80652, 1 }, { 80703, 68 }, { 80754, 68 }, { 80754, 68 }, { 80860, 68 }, { 81055, 68 }, { 81087, 4 }, { 81210, 68 }, { 81211, 1 }, { 81216, 1 }, { 81223, 1 }, { 81231, 1 }, { 81288, 68 }, { 81317, 68 }, { 81327, 65 }, { 81332, 68 }, + { 81376, 68 }, { 81469, 68 }, { 81579, 68 }, { 81617, 1 }, { 81630, 68 }, { 81666, 68 }, { 81800, 68 }, { 81832, 68 }, { 81848, 68 }, { 81869, 68 }, { 81941, 3 }, { 82177, 3 }, { 82179, 68 }, { 82180, 68 }, { 82182, 4 }, { 82185, 68 }, { 82195, 68 }, { 82238, 4 }, { 82265, 3 }, { 82295, 10 }, { 82299, 9 }, { 82367, 3 }, + { 82379, 3 }, { 82380, 1 }, { 82505, 68 }, { 82568, 68 }, { 82620, 1 }, { 82637, 5 }, { 82821, 68 }, { 82841, 68 }, { 82945, 1 }, { 83020, 168 }, { 83072, 68 }, { 83181, 68 }, { 83240, 68 }, { 83253, 3 }, { 83261, 68 }, { 83288, 68 }, { 83291, 4 }, { 83295, 3 }, { 83365, 68 }, { 83368, 68 }, { 83408, 68 }, { 83458, 68 }, + { 83470, 68 }, { 83471, 1 }, { 83637, 3 }, { 83693, 68 }, { 83703, 68 }, { 83732, 68 }, { 83745, 1 }, { 83800, 4 }, { 83801, 3 }, { 83856, 3 }, { 83863, 5 }, { 83867, 68 }, { 83868, 3 }, { 83898, 7 }, { 83900, 4 }, { 83901, 5 }, { 83989, 68 }, { 84049, 35 }, { 84086, 68 }, { 84089, 68 }, { 84115, 3 }, { 84130, 3 }, { 84132, 68 }, + { 84143, 54 }, { 84173, 68 }, { 84185, 5 }, { 84297, 68 }, { 84390, 68 }, { 84497, 4 }, { 84657, 68 }, { 84657, 68 }, { 84724, 68 }, { 84775, 68 }, { 84870, 68 }, { 84892, 68 }, { 84910, 3 }, { 84935, 3 }, { 85002, 68 }, { 85051, 68 }, { 85052, 68 }, { 85135, 25 }, { 85135, 68 }, { 85144, 68 }, { 85165, 3 }, { 85205, 68 }, + { 85232, 68 }, { 85281, 5 }, { 85423, 6 }, { 85539, 68 }, { 85582, 4 }, { 85609, 68 }, { 85701, 36 }, { 85705, 68 }, { 85824, 68 }, { 85824, 68 }, { 85858, 30 }, { 85858, 28 }, { 85904, 35 }, { 85910, 68 }, { 85913, 68 }, { 85926, 3 }, { 85942, 4 }, { 85969, 4 }, { 85996, 1 }, { 86013, 3 }, { 86034, 13 }, { 86068, 8 }, + { 86069, 8 }, { 86089, 8 }, { 86193, 13 }, { 86217, 7 }, { 86219, 68 }, { 86250, 68 }, { 86304, 16 }, { 86317, 68 }, { 86322, 4 }, { 86325, 68 }, { 86333, 68 }, { 86394, 68 }, { 86433, 68 }, { 86469, 3 }, { 86512, 4 }, { 86537, 68 }, { 86627, 68 }, { 86658, 68 }, { 86810, 68 }, { 86813, 68 }, { 86884, 68 }, { 86947, 68 }, + { 87003, 68 }, { 87010, 5 }, { 87019, 68 }, { 87027, 68 }, { 87105, 68 }, { 87107, 68 }, { 87183, 68 }, { 87273, 68 }, { 87358, 3 }, { 87388, 3 }, { 87503, 4 }, { 87639, 68 }, { 87649, 4 }, { 87722, 68 }, { 87829, 68 }, { 87829, 1 }, { 87863, 68 }, { 87894, 68 }, { 87988, 368 }, { 88035, 27 }, { 88059, 3 }, { 88094, 5 }, + { 88111, 21 }, { 88129, 68 }, { 88175, 5 }, { 88256, 68 }, { 88329, 76 }, { 88415, 3 }, { 88482, 68 }, { 88502, 1 }, { 88529, 68 }, { 88551, 3 }, { 88552, 1 }, { 88713, 68 }, { 88797, 68 }, { 88844, 27 }, { 88925, 5 }, { 88935, 68 }, { 88944, 1 }, { 89073, 68 }, { 89095, 3 }, { 89283, 68 }, { 89294, 3 }, { 89299, 68 }, + { 89324, 68 }, { 89368, 68 }, { 89387, 68 }, { 89464, 68 }, { 89607, 68 }, { 89737, 68 }, { 89791, 68 }, { 89794, 3 }, { 89840, 68 }, { 89849, 3 }, { 89859, 68 }, { 89905, 68 }, { 89952, 38 }, { 90030, 7 }, { 90030, 6 }, { 90031, 1 }, { 90072, 68 }, { 90090, 68 }, { 90146, 3 }, { 90202, 23 }, { 90302, 3 }, { 90328, 14 }, + { 90335, 14 }, { 90338, 8 }, { 90380, 68 }, { 90434, 1 }, { 90482, 68 }, { 90527, 9 }, { 90537, 68 }, { 90545, 68 }, { 90639, 5 }, { 90642, 68 }, { 90709, 68 }, { 90775, 1 }, { 90806, 68 }, { 90845, 19 }, { 90872, 4 }, { 90884, 68 }, { 90910, 68 }, { 90994, 5 }, { 91046, 8 }, { 91059, 8 }, { 91096, 39 }, { 91147, 68 }, + { 91168, 1 }, { 91493, 68 }, { 91513, 3 }, { 91618, 3 }, { 91653, 68 }, { 91817, 68 }, { 91831, 3 }, { 91833, 3 }, { 91885, 68 }, { 91919, 68 }, { 91934, 68 }, { 92245, 1 }, { 92284, 68 }, { 92292, 4 }, { 92369, 3 }, { 92388, 68 }, { 92426, 7 }, { 92720, 14 }, { 92720, 6 }, { 92729, 9 }, { 92733, 13 }, { 92735, 6 }, { 92786, 68 }, + { 92853, 31 }, { 92906, 68 }, { 93031, 7 }, { 93077, 68 }, { 93102, 68 }, { 93109, 68 }, { 93122, 3 }, { 93214, 68 }, { 93330, 68 }, { 93395, 68 }, { 93506, 68 }, { 93564, 9 }, { 93713, 9 }, { 93722, 4 }, { 93840, 68 }, { 93877, 4 }, { 93891, 3 }, { 93948, 68 }, { 93981, 68 }, { 94012, 3 }, { 94033, 68 }, { 94121, 68 }, + { 94165, 368 }, { 94181, 3 }, { 94210, 68 }, { 94216, 68 }, { 94230, 68 }, { 94333, 31 }, { 94433, 3 }, { 94497, 3 }, { 94609, 68 }, { 94623, 68 }, { 94763, 68 }, { 94780, 68 }, { 95287, 68 }, { 95348, 68 }, { 95433, 5 }, { 95446, 68 }, { 95493, 7 }, { 95517, 3 }, { 95580, 68 }, { 95610, 5 }, { 95620, 68 }, { 95678, 3 }, + { 95683, 68 }, { 95689, 68 }, { 95760, 68 }, { 95792, 68 }, { 95850, 68 }, { 95908, 68 }, { 95908, 68 }, { 95967, 68 }, { 96022, 3 }, { 96088, 65 }, { 96460, 68 }, { 96554, 68 }, { 96597, 68 }, { 96763, 68 }, { 96808, 68 }, { 96854, 1 }, { 96963, 1 }, { 97007, 3 }, { 97125, 1 }, { 97128, 68 }, { 97133, 3 }, { 97142, 3 }, + { 97156, 68 }, { 97223, 68 }, { 97244, 68 }, { 97303, 68 }, { 97355, 68 }, { 97356, 3 }, { 97393, 3 }, { 97409, 1 }, { 97451, 68 }, { 97539, 68 }, { 97546, 68 }, { 97553, 68 }, { 97627, 68 }, { 97640, 68 }, { 97650, 6 }, { 97675, 68 }, { 97685, 3 }, { 97773, 68 }, { 97802, 4 }, { 97826, 19 }, { 97860, 68 }, { 97956, 68 }, + { 97958, 68 }, { 97973, 3 }, { 97982, 68 }, { 98039, 68 }, { 98051, 68 }, { 98059, 68 }, { 98088, 68 }, { 98092, 4 }, { 98147, 68 }, { 98147, 68 }, { 98169, 68 }, { 98207, 65 }, { 98277, 1 }, { 98277, 268 }, { 98285, 68 }, { 98324, 3 }, { 98324, 3 }, { 98381, 31 }, { 98390, 68 }, { 98404, 68 }, { 98415, 4 }, { 98460, 68 }, + { 98462, 1 }, { 98475, 3 }, { 98485, 68 }, { 98640, 1 }, { 98798, 68 }, { 98800, 4 }, { 98821, 68 }, { 98895, 68 }, { 98936, 68 }, { 98950, 68 }, { 98980, 68 }, { 99033, 68 }, { 99045, 68 }, { 99135, 68 }, { 99315, 30 }, { 99324, 68 }, { 99346, 68 }, { 99418, 68 }, { 99505, 68 }, { 99557, 68 }, { 99559, 68 }, { 99586, 68 }, + { 99622, 68 }, { 99770, 1 }, { 99790, 68 }, { 99810, 68 }, { 99871, 1 }, { 99926, 68 }, { 99927, 68 }, { 99978, 68 }, { 99980, 68 }, { 100022, 3 }, { 100024, 1 }, { 100069, 68 }, { 100150, 68 }, { 100225, 63 }, { 100246, 1 }, { 100310, 68 }, { 100361, 68 }, { 100428, 1 }, { 100434, 68 }, { 100450, 4 }, { 100546, 68 }, + { 100551, 68 }, { 100551, 68 }, { 100554, 1 }, { 100597, 68 }, { 100676, 68 }, { 100693, 68 }, { 100827, 68 }, { 100928, 68 }, { 100928, 1 }, { 100935, 68 }, { 100937, 3 }, { 101034, 68 }, { 101041, 68 }, { 101154, 68 }, { 101200, 4 }, { 101250, 68 }, { 101352, 68 }, { 101403, 68 }, { 101430, 1 }, { 101508, 3 }, { 101509, 3 }, + { 101523, 10 }, { 101604, 68 }, { 101637, 68 }, { 101681, 4 }, { 101759, 1 }, { 101773, 1 }, { 101836, 1 }, { 101882, 4 }, { 101895, 68 }, { 101897, 68 }, { 101939, 68 }, { 101951, 6 }, { 101956, 5 }, { 102055, 1 }, { 102085, 68 }, { 102093, 67 }, { 102209, 68 }, { 102258, 6 }, { 102271, 68 }, { 102284, 68 }, { 102332, 68 }, + { 102354, 68 }, { 102366, 68 }, { 102424, 3 }, { 102456, 68 }, { 102496, 1 }, { 102497, 3 }, { 102519, 3 }, { 102554, 1 }, { 102610, 5 }, { 102657, 68 }, { 102661, 4 }, { 102695, 4 }, { 102707, 168 }, { 102910, 68 }, { 102930, 5 }, { 102937, 9 }, { 102938, 7 }, { 102965, 6 }, { 102969, 7 }, { 103031, 68 }, { 103062, 68 }, + { 103096, 68 }, { 103146, 68 }, { 103159, 68 }, { 103223, 68 }, { 103267, 68 }, { 103296, 68 }, { 103303, 68 }, { 103487, 68 }, { 103491, 68 }, { 103599, 68 }, { 103677, 68 }, { 103903, 1 }, { 104040, 68 }, { 104047, 1 }, { 104052, 68 }, { 104057, 4 }, { 104057, 68 }, { 104062, 98 }, { 104091, 68 }, { 104189, 3 }, { 104283, 8 }, + { 104288, 4 }, { 104305, 3 }, { 104445, 68 }, { 104472, 68 }, { 104475, 1 }, { 104497, 4 }, { 104548, 68 }, { 104582, 68 }, { 104626, 1 }, { 104716, 68 }, { 104826, 68 }, { 104849, 68 }, { 104872, 1 }, { 104945, 1 }, { 104948, 68 }, { 105066, 68 }, { 105071, 1 }, { 105198, 4 }, { 105198, 4 }, { 105203, 68 }, { 105256, 6 }, + { 105263, 68 }, { 105329, 68 }, { 105515, 68 }, { 105566, 68 }, { 105566, 68 }, { 105585, 68 }, { 105678, 68 }, { 105852, 68 }, { 105877, 68 }, { 105911, 68 }, { 106022, 1 }, { 106033, 68 }, { 106080, 68 }, { 106192, 68 }, { 106220, 3 }, { 106243, 68 }, { 106323, 11 }, { 106371, 68 }, { 106608, 68 }, { 106624, 87 }, { 106680, 3 }, + { 106688, 1 }, { 106800, 1 }, { 106800, 1 }, { 106821, 4 }, { 106853, 1 }, { 106930, 3 }, { 106937, 68 }, { 106955, 68 }, { 106996, 68 }, { 106996, 1 }, { 107148, 4 }, { 107213, 16 }, { 107213, 68 }, { 107243, 68 }, { 107360, 68 }, { 107408, 68 }, { 107509, 4 }, { 107572, 68 }, { 107592, 68 }, { 107644, 5 }, { 107679, 68 }, + { 107705, 3 }, { 107761, 4 }, { 107780, 68 }, { 107825, 68 }, { 108007, 68 }, { 108041, 4 }, { 108058, 68 }, { 108071, 1 }, { 108132, 68 }, { 108164, 68 }, { 108189, 68 }, { 108210, 68 }, { 108330, 68 }, { 108430, 68 }, { 108450, 68 }, { 108469, 68 }, { 108484, 68 }, { 108533, 68 }, { 108588, 68 }, { 108594, 68 }, { 108690, 68 }, + { 108785, 76 }, { 108814, 68 }, { 108818, 1 }, { 108820, 68 }, { 108889, 68 }, { 108951, 68 }, { 108959, 68 }, { 108963, 68 }, { 109034, 68 }, { 109172, 1 }, { 109176, 68 }, { 109195, 3 }, { 109229, 68 }, { 109256, 68 }, { 109290, 68 }, { 109304, 68 }, { 109333, 68 }, { 109343, 4 }, { 109347, 7 }, { 109387, 68 }, { 109421, 1 }, + { 109497, 68 }, { 109501, 3 }, { 109513, 68 }, { 109525, 3 }, { 109625, 4 }, { 109710, 68 }, { 109740, 68 }, { 109751, 68 }, { 109761, 68 }, { 109890, 8 }, { 109891, 4 }, { 109909, 68 }, { 109923, 1 }, { 110017, 68 }, { 110046, 68 }, { 110111, 68 }, { 110258, 68 }, { 110340, 68 }, { 110352, 68 }, { 110398, 68 }, { 110583, 68 }, + { 110600, 13 }, { 110626, 3 }, { 110709, 68 }, { 110772, 4 }, { 110773, 68 }, { 110813, 1 }, { 110890, 68 }, { 110898, 68 }, { 110954, 68 }, { 111120, 68 }, { 111132, 3 }, { 111163, 8 }, { 111224, 68 }, { 111340, 68 }, { 111398, 68 }, { 111555, 68 }, { 111597, 3 }, { 111607, 68 }, { 111655, 68 }, { 111691, 3 }, { 111835, 68 }, + { 111854, 68 }, { 111876, 16 }, { 111884, 1 }, { 111884, 56 }, { 111929, 68 }, { 111941, 68 }, { 111969, 68 }, { 112003, 68 }, { 112165, 68 }, { 112365, 68 }, { 112450, 1 }, { 112521, 68 }, { 112649, 4 }, { 112665, 68 }, { 112881, 1 }, { 112882, 68 }, { 112906, 68 }, { 112951, 68 }, { 112994, 68 }, { 112997, 68 }, { 113002, 68 }, + { 113056, 1 }, { 113077, 68 }, { 113208, 1 }, { 113320, 68 }, { 113326, 3 }, { 113375, 68 }, { 113530, 30 }, { 113530, 30 }, { 113537, 1 }, { 113563, 14 }, { 113592, 68 }, { 113637, 68 }, { 113768, 68 }, { 113850, 5 }, { 113892, 68 }, { 113916, 68 }, { 113965, 68 }, { 113976, 68 }, { 114037, 68 }, { 114149, 1 }, { 114158, 9 }, + { 114201, 68 }, { 114262, 68 }, { 114268, 4 }, { 114353, 68 }, { 114388, 68 }, { 114404, 68 }, { 114428, 5 }, { 114438, 68 }, { 114541, 68 }, { 114550, 68 }, { 114561, 68 }, { 114625, 3 }, { 114730, 68 }, { 114770, 1 }, { 114815, 4 }, { 114998, 68 }, { 115077, 68 }, { 115093, 68 }, { 115120, 68 }, { 115194, 68 }, { 115216, 3 }, + { 115299, 68 }, { 115391, 3 }, { 115410, 68 }, { 115542, 33 }, { 115581, 68 }, { 115618, 68 }, { 115645, 23 }, { 115647, 68 }, { 115697, 68 }, { 115725, 68 }, { 115740, 68 }, { 115757, 68 }, { 115763, 68 }, { 115770, 68 }, { 115787, 68 }, { 115916, 68 }, { 115928, 68 }, { 115962, 68 }, { 116020, 68 }, { 116022, 1 }, { 116089, 68 }, + { 116159, 1 }, { 116196, 68 }, { 116247, 68 }, { 116254, 7 }, { 116336, 68 }, { 116409, 68 }, { 116459, 68 }, { 116569, 68 }, { 116619, 68 }, { 116688, 68 }, { 116733, 68 }, { 116807, 3 }, { 116843, 68 }, { 116886, 1 }, { 116902, 68 }, { 116931, 68 }, { 116952, 68 }, { 116952, 68 }, { 117177, 68 }, { 117189, 68 }, { 117206, 68 }, + { 117260, 29 }, { 117271, 6 }, { 117276, 3 }, { 117276, 5 }, { 117278, 3 }, { 117278, 68 }, { 117359, 4 }, { 117380, 68 }, { 117414, 1 }, { 117503, 68 }, { 117517, 68 }, { 117530, 68 }, { 117574, 4 }, { 117575, 5 }, { 117577, 68 }, { 117606, 68 }, { 117645, 68 }, { 117655, 68 }, { 117692, 68 }, { 117705, 1 }, { 117731, 1 }, + { 117762, 4 }, { 117780, 68 }, { 117974, 1 }, { 118057, 1 }, { 118099, 68 }, { 118107, 68 }, { 118113, 68 }, { 118175, 68 }, { 118198, 68 }, { 118232, 45 }, { 118326, 1 }, { 118438, 31 }, { 118469, 68 }, { 118521, 31 }, { 118565, 68 }, { 118593, 68 }, { 118602, 68 }, { 118652, 68 }, { 118668, 68 }, { 118689, 3 }, { 118703, 14 }, + { 118705, 68 }, { 118813, 68 }, { 118825, 68 }, { 118894, 3 }, { 118915, 68 }, { 118962, 68 }, { 118986, 68 }, { 119045, 68 }, { 119054, 1 }, { 119054, 1 }, { 119119, 68 }, { 119149, 68 }, { 119206, 1 }, { 119316, 68 }, { 119387, 68 }, { 119404, 3 }, { 119516, 68 }, { 119520, 68 }, { 119571, 3 }, { 119573, 68 }, { 119610, 5 }, + { 119621, 68 }, { 119623, 4 }, { 119672, 68 }, { 119692, 3 }, { 119734, 68 }, { 119742, 1 }, { 119754, 1 }, { 119785, 68 }, { 120001, 68 }, { 120115, 4 }, { 120260, 68 }, { 120314, 68 }, { 120416, 68 }, { 120435, 1 }, { 120450, 3 }, { 120530, 68 }, { 120550, 5 }, { 120730, 68 }, { 120731, 68 }, { 120751, 3 }, { 120755, 68 }, + { 120869, 68 }, { 120988, 68 }, { 121061, 68 }, { 121177, 68 }, { 121212, 68 }, { 121214, 1 }, { 121286, 68 }, { 121331, 1 }, { 121344, 68 }, { 121407, 68 }, { 121424, 1 }, { 121491, 68 }, { 121568, 76 }, { 121588, 6 }, { 121651, 68 }, { 121676, 68 }, { 121785, 4 }, { 121830, 3 }, { 121919, 1 }, { 121951, 68 }, { 121991, 1 }, + { 122056, 68 }, { 122062, 68 }, { 122144, 68 }, { 122183, 1 }, { 122331, 68 }, { 122466, 68 }, { 122558, 68 }, { 122570, 68 }, { 122676, 68 }, { 122733, 68 }, { 122774, 6 }, { 122783, 68 }, { 122825, 68 }, { 122865, 68 }, { 122884, 68 }, { 122892, 68 }, { 122911, 68 }, { 122929, 68 }, { 122936, 68 }, { 123190, 68 }, { 123271, 68 }, + { 123271, 68 }, { 123302, 7 }, { 123391, 68 }, { 123394, 68 }, { 123416, 1 }, { 123708, 68 }, { 123752, 68 }, { 123761, 68 }, { 123783, 68 }, { 123794, 68 }, { 123817, 68 }, { 123820, 1 }, { 123823, 1 }, { 123857, 3 }, { 123886, 56 }, { 124023, 1 }, { 124029, 68 }, { 124042, 68 }, { 124056, 3 }, { 124071, 6 }, { 124105, 5 }, + { 124143, 68 }, { 124191, 68 }, { 124207, 1 }, { 124257, 68 }, { 124306, 3 }, { 124338, 68 }, { 124388, 8 }, { 124400, 68 }, { 124418, 68 }, { 124502, 68 }, { 124521, 1 }, { 124533, 68 }, { 124645, 68 }, { 124685, 1 }, { 124694, 68 }, { 124700, 1 }, { 124736, 68 }, { 124891, 7 }, { 124920, 68 }, { 124983, 68 }, { 125014, 68 }, + { 125038, 68 }, { 125084, 68 }, { 125162, 68 }, { 125193, 68 }, { 125285, 68 }, { 125368, 68 }, { 125409, 68 }, { 125570, 68 }, { 125601, 68 }, { 125641, 1 }, { 125721, 68 }, { 125731, 68 }, { 125803, 68 }, { 125904, 68 }, { 125973, 68 }, { 126018, 1 }, { 126034, 5 }, { 126094, 1 }, { 126144, 1 }, { 126195, 68 }, { 126297, 68 }, + { 126389, 68 }, { 126429, 68 }, { 126439, 68 }, { 126499, 68 }, { 126501, 1 }, { 126587, 68 }, { 126663, 68 }, { 126681, 68 }, { 126687, 1 }, { 126781, 68 }, { 126783, 68 }, { 126840, 8 }, { 126843, 68 }, { 126959, 68 }, { 127015, 68 }, { 127101, 68 }, { 127149, 68 }, { 127197, 54 }, { 127268, 68 }, { 127372, 68 }, { 127385, 68 }, + { 127473, 4 }, { 127539, 68 }, { 127598, 68 }, { 127613, 14 }, { 127683, 3 }, { 127684, 68 }, { 127697, 68 }, { 127698, 3 }, { 127773, 68 }, { 127781, 1 }, { 127839, 68 }, { 127905, 68 }, { 127949, 68 }, { 128035, 68 }, { 128046, 1 }, { 128167, 68 }, { 128271, 68 }, { 128307, 1 }, { 128320, 68 }, { 128330, 68 }, { 128375, 68 }, + { 128381, 4 }, { 128447, 68 }, { 128462, 68 }, { 128466, 3 }, { 128466, 68 }, { 128496, 68 }, { 128589, 68 }, { 128616, 3 }, { 128679, 1 }, { 128770, 1 }, { 128793, 68 }, { 128802, 68 }, { 128813, 68 }, { 128900, 68 }, { 128949, 68 }, { 129269, 68 }, { 129271, 3 }, { 129278, 68 }, { 129343, 1 }, { 129408, 67 }, { 129408, 1 }, + { 129421, 6 }, { 129461, 68 }, { 129469, 3 }, { 129482, 68 }, { 129502, 68 }, { 129512, 68 }, { 129551, 68 }, { 129629, 68 }, { 129632, 68 }, { 129679, 1 }, { 129725, 68 }, { 130007, 68 }, { 130018, 16 }, { 130057, 68 }, { 130071, 68 }, { 130087, 68 }, { 130188, 1 }, { 130202, 68 }, { 130316, 68 }, { 130328, 1 }, { 130466, 68 }, + { 130549, 68 }, { 130649, 68 }, { 130705, 3 }, { 130800, 68 }, { 130907, 68 }, { 130989, 68 }, { 131103, 68 }, { 131127, 68 }, { 131200, 5 }, { 131241, 6 }, { 131351, 68 }, { 131413, 68 }, { 131448, 68 }, { 131599, 68 }, { 131634, 1 }, { 131687, 68 }, { 131739, 68 }, { 131758, 68 }, { 131765, 68 }, { 131787, 3 }, { 131819, 3 }, + { 131868, 68 }, { 131886, 68 }, { 131901, 4 }, { 131977, 68 }, { 131990, 68 }, { 132035, 68 }, { 132035, 68 }, { 132043, 68 }, { 132173, 68 }, { 132181, 4 }, { 132181, 6 }, { 132194, 5 }, { 132252, 68 }, { 132262, 6 }, { 132271, 1 }, { 132285, 68 }, { 132328, 68 }, { 132335, 1 }, { 132337, 1 }, { 132389, 5 }, { 132430, 68 }, + { 132451, 68 }, { 132499, 87 }, { 132503, 1 }, { 132520, 4 }, { 132541, 4 }, { 132860, 68 }, { 132862, 4 }, { 132874, 168 }, { 132874, 13 }, { 132875, 168 }, { 132911, 68 }, { 132973, 68 }, { 133051, 68 }, { 133062, 68 }, { 133067, 68 }, { 133138, 68 }, { 133184, 68 }, { 133231, 68 }, { 133297, 3 }, { 133344, 68 }, { 133385, 4 }, + { 133408, 68 }, { 133464, 68 }, { 133522, 68 }, { 133631, 68 }, { 133631, 68 }, { 133702, 68 }, { 133705, 1 }, { 133721, 68 }, { 133746, 68 }, { 133773, 3 }, { 133819, 68 }, { 133843, 68 }, { 133929, 68 }, { 133946, 68 }, { 134113, 4 }, { 134151, 68 }, { 134289, 1 }, { 134385, 68 }, { 134429, 68 }, { 134506, 68 }, { 134511, 68 }, + { 134521, 68 }, { 134558, 1 }, { 134710, 68 }, { 134738, 68 }, { 134751, 3 }, { 134818, 68 }, { 134820, 4 }, { 134879, 68 }, { 134919, 68 }, { 134947, 68 }, { 134948, 3 }, { 135040, 3 }, { 135125, 10 }, { 135155, 68 }, { 135228, 68 }, { 135255, 68 }, { 135296, 3 }, { 135322, 68 }, { 135349, 68 }, { 135428, 3 }, { 135476, 1 }, + { 135503, 68 }, { 135524, 68 }, { 135550, 4 }, { 135594, 68 }, { 135597, 68 }, { 135624, 3 }, { 135741, 68 }, { 135753, 68 }, { 135842, 68 }, { 135853, 68 }, { 135896, 3 }, { 136004, 1 }, { 136061, 1 }, { 136068, 1 }, { 136106, 68 }, { 136145, 68 }, { 136145, 68 }, { 136173, 68 }, { 136186, 68 }, { 136196, 68 }, { 136201, 68 }, + { 136211, 68 }, { 136268, 68 }, { 136298, 68 }, { 136377, 68 }, { 136420, 68 }, { 136475, 23 }, { 136486, 1 }, { 136554, 68 }, { 136641, 68 }, { 136770, 1 }, { 136873, 68 }, { 136877, 1 }, { 136906, 68 }, { 137092, 68 }, { 137143, 68 }, { 137200, 3 }, { 137232, 68 }, { 137239, 68 }, { 137248, 68 }, { 137281, 1 }, { 137301, 68 }, + { 137314, 3 }, { 137352, 1 }, { 137365, 68 }, { 137375, 68 }, { 137411, 68 }, { 137424, 68 }, { 137516, 68 }, { 137532, 68 }, { 137593, 68 }, { 137600, 68 }, { 137658, 68 }, { 137703, 68 }, { 137766, 68 }, { 137791, 68 }, { 137801, 68 }, { 137864, 68 }, { 137870, 3 }, { 137931, 68 }, { 138009, 3 }, { 138013, 1 }, { 138013, 1 }, + { 138066, 68 }, { 138073, 68 }, { 138114, 68 }, { 138150, 68 }, { 138236, 68 }, { 138276, 68 }, { 138286, 68 }, { 138298, 3 }, { 138309, 1 }, { 138373, 3 }, { 138524, 68 }, { 138535, 1 }, { 138593, 4 }, { 138611, 1 }, { 138725, 68 }, { 138807, 68 }, { 138819, 3 }, { 138849, 5 }, { 138867, 68 }, { 138907, 68 }, { 138930, 3 }, + { 139026, 68 }, { 139102, 68 }, { 139108, 3 }, { 139184, 1 }, { 139209, 3 }, { 139282, 68 }, { 139289, 4 }, { 139382, 1 }, { 139421, 1 }, { 139436, 68 }, { 139450, 1 }, { 139523, 3 }, { 139533, 68 }, { 139590, 68 }, { 139590, 68 }, { 139722, 68 }, { 139785, 68 }, { 139785, 1 }, { 139798, 68 }, { 139813, 68 }, { 139868, 68 }, + { 139935, 3 }, { 139990, 3 }, { 140050, 68 }, { 140177, 68 }, { 140177, 4 }, { 140408, 68 }, { 140420, 3 }, { 140461, 68 }, { 140578, 15 }, { 140605, 1368 }, { 140662, 1 }, { 140755, 68 }, { 140786, 68 }, { 140846, 68 }, { 140874, 68 }, { 140959, 1 }, { 140973, 68 }, { 141128, 68 }, { 141132, 68 }, { 141257, 68 }, { 141290, 1 }, + { 141360, 68 }, { 141472, 68 }, { 141545, 68 }, { 141545, 68 }, { 141575, 1 }, { 141606, 5 }, { 141655, 68 }, { 141735, 68 }, { 141767, 5 }, { 141796, 68 }, { 141841, 68 }, { 141915, 68 }, { 141923, 1 }, { 141932, 68 }, { 141994, 68 }, { 142018, 68 }, { 142029, 3 }, { 142072, 68 }, { 142128, 68 }, { 142133, 1 }, { 142261, 68 }, + { 142304, 1 }, { 142400, 68 }, { 142401, 68 }, { 142409, 68 }, { 142479, 68 }, { 142522, 1 }, { 142552, 1 }, { 142589, 68 }, { 142596, 68 }, { 142753, 1 }, { 142766, 68 }, { 142796, 68 }, { 142836, 68 }, { 142871, 68 }, { 143058, 3 }, { 143059, 6 }, { 143063, 3 }, { 143065, 68 }, { 143141, 4 }, { 143173, 68 }, { 143374, 68 }, + { 143399, 68 }, { 143406, 68 }, { 143429, 3 }, { 143430, 68 }, { 143462, 1 }, { 143579, 68 }, { 143663, 68 }, { 143844, 3 }, { 143851, 68 }, { 143926, 68 }, { 143931, 68 }, { 144051, 6 }, { 144085, 10 }, { 144147, 68 }, { 144188, 4 }, { 144238, 4 }, { 144353, 68 }, { 144465, 68 }, { 144474, 68 }, { 144637, 68 }, { 144638, 1 }, + { 144648, 1 }, { 144661, 3 }, { 144812, 68 }, { 144847, 68 }, { 144901, 8 }, { 145058, 68 }, { 145122, 8 }, { 145134, 68 }, { 145150, 68 }, { 145299, 1 }, { 145313, 68 }, { 145314, 3 }, { 145374, 68 }, { 145412, 68 }, { 145432, 68 }, { 145446, 68 }, { 145534, 3 }, { 145592, 68 }, { 145614, 68 }, { 145648, 68 }, { 145721, 68 }, + { 145858, 1 }, { 145970, 3 }, { 145984, 3 }, { 146004, 68 }, { 146016, 3 }, { 146048, 68 }, { 146097, 3 }, { 146103, 68 }, { 146136, 68 }, { 146194, 3 }, { 146230, 1 }, { 146254, 68 }, { 146261, 4 }, { 146269, 4 }, { 146393, 68 }, { 146411, 3 }, { 146501, 68 }, { 146547, 68 }, { 146547, 68 }, { 146573, 68 }, { 146616, 68 }, + { 146622, 3 }, { 146728, 3 }, { 146781, 5 }, { 146805, 4 }, { 146921, 68 }, { 147002, 3 }, { 147072, 68 }, { 147159, 68 }, { 147170, 68 }, { 147203, 1 }, { 147245, 68 }, { 147278, 68 }, { 147422, 68 }, { 147471, 68 }, { 147491, 68 }, { 147607, 23 }, { 147693, 68 }, { 147763, 68 }, { 147775, 6 }, { 147776, 4 }, { 147824, 68 }, + { 147922, 68 }, { 147922, 68 }, { 147937, 68 }, { 147957, 68 }, { 147980, 68 }, { 148008, 68 }, { 148018, 68 }, { 148046, 3 }, { 148071, 4 }, { 148106, 3 }, { 148122, 68 }, { 148139, 68 }, { 148175, 68 }, { 164238, 18 }, { 164315, 28 }, { 164449, 28 }, { 164529, 28 }, { 164574, 348 }, { 164591, 968 }, { 164595, 28 }, + { 164611, 28 }, { 164623, 48 }, { 164632, 108 }, { 164691, 28 }, { 164706, 28 }, { 164755, 28 }, { 164761, 28 }, { 164973, 28 }, { 165030, 28 }, { 165090, 28 }, { 165099, 18 }, { 165126, 28 }, { 165188, 28 }, { 165205, 28 }, { 165275, 18 }, { 165347, 28 }, { 165381, 28 }, { 165562, 28 }, { 165563, 18 }, { 165594, 568 }, + { 165641, 868 }, { 165663, 68 }, { 165759, 28 }, { 165811, 28 }, { 165822, 18 }, { 165830, 18 }, { 165903, 18 }, { 165921, 28 }, { 165953, 18 }, { 166022, 18 }, { 166294, 28 }, { 166333, 28 }, { 166420, 28 }, { 166433, 28 }, { 166442, 18 }, { 166536, 28 }, { 166543, 28 }, { 166556, 28 }, { 166571, 28 }, { 166575, 18 }, + { 166588, 28 }, { 166601, 678 }, { 166663, 788 }, { 166692, 18 }, { 166710, 28 }, { 166759, 28 }, { 166785, 38 }, { 166842, 28 }, { 166843, 28 }, { 166864, 28 }, { 166902, 28 }, { 166996, 28 }, { 166999, 28 }, { 167038, 28 }, { 167112, 48 }, { 167112, 28 }, { 167177, 28 }, { 167180, 28 }, { 167229, 18 }, { 167298, 28 }, + { 167306, 48 }, { 167309, 38 }, { 167402, 28 }, { 167405, 878 }, { 167433, 568 }, { 167435, 18 }, { 167461, 38 }, { 167553, 38 }, { 167688, 58 }, { 167689, 28 }, { 167709, 28 }, { 167744, 28 }, { 167821, 28 }, { 167825, 28 }, { 167925, 108 }, { 167969, 28 }, { 168024, 28 }, { 168089, 28 }, { 168104, 28 }, { 168194, 28 }, + { 168305, 28 }, { 168316, 28 }, { 168366, 28 }, { 168423, 28 }, { 168568, 38 }, { 168582, 558 }, { 168615, 768 }, { 168618, 28 }, { 168638, 28 }, { 168671, 28 }, { 168736, 28 }, { 168747, 28 }, { 168750, 48 }, { 168808, 38 }, { 168814, 48 }, { 168820, 28 }, { 168914, 28 }, { 168968, 28 }, { 168979, 28 }, { 169006, 28 }, + { 169069, 28 }, { 169106, 38 }, { 169158, 28 }, { 169158, 28 }, { 169189, 28 }, { 169253, 28 }, { 169259, 18 }, { 169279, 658 }, { 169325, 568 }, { 169349, 28 }, { 169353, 28 }, { 169378, 28 }, { 169432, 28 }, { 169476, 18 }, { 169476, 18 }, { 169525, 28 }, { 169538, 78 }, { 169555, 28 }, { 169571, 28 }, { 169594, 48 }, + { 169687, 28 }, { 169799, 28 }, { 169831, 28 }, { 170042, 28 }, { 170061, 28 }, { 170065, 18 }, { 170128, 68 }, { 170148, 208 }, { 170215, 708 }, { 170256, 608 }, { 170266, 698 }, { 170275, 78 }, { 170277, 68 }, { 170500, 38 }, { 170516, 38 }, { 170601, 28 }, { 170666, 28 }, { 170668, 48 }, { 170668, 18 }, { 170716, 38 }, + { 170728, 38 }, { 170735, 58 }, { 170847, 38 }, { 170852, 98 }, { 170858, 438 }, { 170859, 568 }, { 170956, 568 }, { 170956, 18 }, { 170967, 28 }, { 171005, 28 }, { 171113, 28 }, { 171279, 28 }, { 171400, 28 }, { 171405, 28 }, { 171448, 18 }, { 171490, 28 }, { 171567, 328 }, { 171590, 28 }, { 171723, 28 }, { 171737, 38 }, + { 171958, 28 }, { 171967, 68 }, { 164238, 18 }, { 164315, 28 }, { 164449, 28 }, { 164529, 28 }, { 164574, 348 }, { 164591, 968 }, { 164595, 28 }, { 164611, 28 }, { 164623, 48 }, { 164632, 108 }, { 164691, 28 }, { 164706, 28 }, { 164755, 28 }, { 164761, 28 }, { 164973, 28 }, { 165030, 28 }, { 165090, 28 }, { 165099, 18 }, + { 165126, 28 }, { 165188, 28 }, { 165205, 28 }, { 165275, 18 }, { 165347, 28 }, { 165381, 28 }, { 165562, 28 }, { 165563, 18 }, { 165594, 568 }, { 165641, 868 }, { 165663, 68 }, { 165759, 28 }, { 165811, 28 }, { 165822, 18 }, { 165830, 18 }, { 165903, 18 }, { 165921, 28 }, { 165953, 18 }, { 166022, 18 }, { 166294, 28 }, + { 166333, 28 }, { 166420, 28 }, { 166433, 28 }, { 166442, 18 }, { 166536, 28 }, { 166543, 28 }, { 166556, 28 }, { 166571, 28 }, { 166575, 18 }, { 166588, 28 }, { 166601, 678 }, { 166663, 788 }, { 166692, 18 }, { 166710, 28 }, { 166759, 28 }, { 166785, 38 }, { 166842, 28 }, { 166843, 28 }, { 166864, 28 }, { 166902, 28 }, + { 166996, 28 }, { 166999, 28 }, { 167038, 28 }, { 167112, 48 }, { 167112, 28 }, { 167177, 28 }, { 167180, 28 }, { 167229, 18 }, { 167298, 28 }, { 167306, 48 }, { 167309, 38 }, { 167402, 28 }, { 167405, 878 }, { 167433, 568 }, { 167435, 18 }, { 167461, 38 }, { 167553, 38 }, { 167688, 58 }, { 167689, 28 }, { 167709, 28 }, + { 167744, 28 }, { 167821, 28 }, { 167825, 28 }, { 167925, 108 }, { 167969, 28 }, { 168024, 28 }, { 168089, 28 }, { 168104, 28 }, { 168194, 28 }, { 168305, 28 }, { 168316, 28 }, { 168366, 28 }, { 168423, 28 }, { 168568, 38 }, { 168582, 558 }, { 168615, 768 }, { 168618, 28 }, { 168638, 28 }, { 168671, 28 }, { 168736, 28 }, + { 168747, 28 }, { 168750, 48 }, { 168808, 38 }, { 168814, 48 }, { 168820, 28 }, { 168914, 28 }, { 168968, 28 }, { 168979, 28 }, { 169006, 28 }, { 169069, 28 }, { 169106, 38 }, { 169158, 28 }, { 169158, 28 }, { 169189, 28 }, { 169253, 28 }, { 169259, 18 }, { 169279, 658 }, { 169325, 568 }, { 169349, 28 }, { 169353, 28 }, + { 169378, 28 }, { 169432, 28 }, { 169476, 18 }, { 169476, 18 }, { 169525, 28 }, { 169538, 78 }, { 169555, 28 }, { 169571, 28 }, { 169594, 48 }, { 169687, 28 }, { 169799, 28 }, { 169831, 28 }, { 170042, 28 }, { 170061, 28 }, { 170065, 18 }, { 170128, 68 }, { 170148, 208 }, { 170215, 708 }, { 170256, 608 }, { 170266, 698 }, + { 170275, 78 }, { 170277, 68 }, { 170500, 38 }, { 170516, 38 }, { 170601, 28 }, { 170666, 28 }, { 170668, 48 }, { 170668, 18 }, { 170716, 38 }, { 170728, 38 }, { 170735, 58 }, { 170847, 38 }, { 170852, 98 }, { 170858, 438 }, { 170859, 568 }, { 170956, 568 }, { 170956, 18 }, { 170967, 28 }, { 171005, 28 }, { 171113, 28 }, + { 171279, 28 }, { 171400, 28 }, { 171405, 28 }, { 171448, 18 }, { 171490, 28 }, { 171567, 328 }, { 171590, 28 }, { 171723, 28 }, { 171737, 38 }, { 171958, 28 }, { 171967, 2 } }; + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixTimerTest.java b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixTimerTest.java new file mode 100644 index 0000000..8d873cf --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixTimerTest.java @@ -0,0 +1,283 @@ +/** + * 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.util; + +import com.netflix.hystrix.HystrixTimerThreadPoolProperties; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.util.HystrixTimer.ScheduledExecutor; +import com.netflix.hystrix.util.HystrixTimer.TimerListener; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.lang.ref.Reference; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + + +public class HystrixTimerTest { + + @Before + public void setUp() { + HystrixTimer timer = HystrixTimer.getInstance(); + HystrixTimer.reset(); + HystrixPlugins.reset(); + } + + @After + public void tearDown() { + HystrixPlugins.reset(); + } + + @Test + public void testSingleCommandSingleInterval() { + HystrixTimer timer = HystrixTimer.getInstance(); + TestListener l1 = new TestListener(50, "A"); + timer.addTimerListener(l1); + + TestListener l2 = new TestListener(50, "B"); + timer.addTimerListener(l2); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // we should have 7 or more 50ms ticks within 500ms + System.out.println("l1 ticks: " + l1.tickCount.get()); + System.out.println("l2 ticks: " + l2.tickCount.get()); + assertTrue(l1.tickCount.get() > 7); + assertTrue(l2.tickCount.get() > 7); + } + + @Test + public void testSingleCommandMultipleIntervals() { + HystrixTimer timer = HystrixTimer.getInstance(); + TestListener l1 = new TestListener(100, "A"); + timer.addTimerListener(l1); + + TestListener l2 = new TestListener(10, "B"); + timer.addTimerListener(l2); + + TestListener l3 = new TestListener(25, "C"); + timer.addTimerListener(l3); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // we should have 3 or more 100ms ticks within 500ms + System.out.println("l1 ticks: " + l1.tickCount.get()); + assertTrue(l1.tickCount.get() >= 3); + // but it can't be more than 6 + assertTrue(l1.tickCount.get() < 6); + + // we should have 30 or more 10ms ticks within 500ms + System.out.println("l2 ticks: " + l2.tickCount.get()); + assertTrue(l2.tickCount.get() > 30); + assertTrue(l2.tickCount.get() < 550); + + // we should have 15-20 25ms ticks within 500ms + System.out.println("l3 ticks: " + l3.tickCount.get()); + assertTrue(l3.tickCount.get() > 14); + assertTrue(l3.tickCount.get() < 25); + } + + @Test + public void testSingleCommandRemoveListener() { + HystrixTimer timer = HystrixTimer.getInstance(); + TestListener l1 = new TestListener(50, "A"); + timer.addTimerListener(l1); + + TestListener l2 = new TestListener(50, "B"); + Reference l2ref = timer.addTimerListener(l2); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // we should have 7 or more 50ms ticks within 500ms + System.out.println("l1 ticks: " + l1.tickCount.get()); + System.out.println("l2 ticks: " + l2.tickCount.get()); + assertTrue(l1.tickCount.get() > 7); + assertTrue(l2.tickCount.get() > 7); + + // remove l2 + l2ref.clear(); + + // reset counts + l1.tickCount.set(0); + l2.tickCount.set(0); + + // wait for time to pass again + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // we should have 7 or more 50ms ticks within 500ms + System.out.println("l1 ticks: " + l1.tickCount.get()); + System.out.println("l2 ticks: " + l2.tickCount.get()); + // l1 should continue ticking + assertTrue(l1.tickCount.get() > 7); + // we should have no ticks on l2 because we removed it + System.out.println("tickCount.get(): " + l2.tickCount.get() + " on l2: " + l2); + assertEquals(0, l2.tickCount.get()); + } + + @Test + public void testReset() { + HystrixTimer timer = HystrixTimer.getInstance(); + TestListener l1 = new TestListener(50, "A"); + timer.addTimerListener(l1); + + ScheduledExecutor ex = timer.executor.get(); + + assertFalse(ex.executor.isShutdown()); + + // perform reset which should shut it down + HystrixTimer.reset(); + + assertTrue(ex.executor.isShutdown()); + assertNull(timer.executor.get()); + + // assert it starts up again on use + TestListener l2 = new TestListener(50, "A"); + timer.addTimerListener(l2); + + ScheduledExecutor ex2 = timer.executor.get(); + + assertFalse(ex2.executor.isShutdown()); + + // reset again to shutdown what we just started + HystrixTimer.reset(); + // try resetting again to make sure it's idempotent (ie. doesn't blow up on an NPE) + HystrixTimer.reset(); + } + + @Test + public void testThreadPoolSizeDefault() { + + HystrixTimer hystrixTimer = HystrixTimer.getInstance(); + hystrixTimer.startThreadIfNeeded(); + assertEquals(Runtime.getRuntime().availableProcessors(), hystrixTimer.executor.get().getThreadPool().getCorePoolSize()); + } + + @Test + public void testThreadPoolSizeConfiguredWithBuilder() { + + HystrixTimerThreadPoolProperties.Setter builder = HystrixTimerThreadPoolProperties.Setter().withCoreSize(1); + final HystrixTimerThreadPoolProperties props = new HystrixTimerThreadPoolProperties(builder) { + }; + + HystrixPropertiesStrategy strategy = new HystrixPropertiesStrategy() { + @Override + public HystrixTimerThreadPoolProperties getTimerThreadPoolProperties() { + return props; + } + }; + + HystrixPlugins.getInstance().registerPropertiesStrategy(strategy); + + HystrixTimer hystrixTimer = HystrixTimer.getInstance(); + hystrixTimer.startThreadIfNeeded(); + + assertEquals(1, hystrixTimer.executor.get().getThreadPool().getCorePoolSize()); + + } + + private static class TestListener implements TimerListener { + + private final int interval; + AtomicInteger tickCount = new AtomicInteger(); + + TestListener(int interval, String value) { + this.interval = interval; + } + + @Override + public void tick() { + tickCount.incrementAndGet(); + } + + @Override + public int getIntervalTimeInMilliseconds() { + return interval; + } + + } + + public static void main(String args[]) { + PlayListener l1 = new PlayListener(); + PlayListener l2 = new PlayListener(); + PlayListener l3 = new PlayListener(); + PlayListener l4 = new PlayListener(); + PlayListener l5 = new PlayListener(); + + Reference ref = HystrixTimer.getInstance().addTimerListener(l1); + HystrixTimer.getInstance().addTimerListener(l2); + HystrixTimer.getInstance().addTimerListener(l3); + + HystrixTimer.getInstance().addTimerListener(l4); + + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + ref.clear(); + HystrixTimer.getInstance().addTimerListener(l5); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("counter: " + l1.counter); + System.out.println("counter: " + l2.counter); + System.out.println("counter: " + l3.counter); + System.out.println("counter: " + l4.counter); + System.out.println("counter: " + l5.counter); + + } + + public static class PlayListener implements TimerListener { + int counter = 0; + + @Override + public void tick() { + counter++; + } + + @Override + public int getIntervalTimeInMilliseconds() { + return 10; + } + + } + + +} diff --git a/Hystrix-master/hystrix-core/src/test/resources/FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties b/Hystrix-master/hystrix-core/src/test/resources/FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties new file mode 100644 index 0000000..e3f6484 --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/resources/FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties @@ -0,0 +1 @@ +com.netflix.hystrix.strategy.HystrixPluginsTest$MockHystrixDynamicPropertiesTest \ No newline at end of file diff --git a/Hystrix-master/hystrix-core/src/test/resources/FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesFail b/Hystrix-master/hystrix-core/src/test/resources/FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesFail new file mode 100644 index 0000000..dbcb94f --- /dev/null +++ b/Hystrix-master/hystrix-core/src/test/resources/FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesFail @@ -0,0 +1 @@ +FAILDONOTWORK \ No newline at end of file diff --git a/Hystrix-master/hystrix-examples-webapp/README.md b/Hystrix-master/hystrix-examples-webapp/README.md new file mode 100644 index 0000000..6e71e4a --- /dev/null +++ b/Hystrix-master/hystrix-examples-webapp/README.md @@ -0,0 +1,19 @@ +# hystrix-examples-webapp + +Web application that demonstrates functionality from [hystrix-examples](https://github.com/Netflix/Hystrix/tree/master/hystrix-examples) and functionality from [hystrix-request-servlet](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-request-servlet) and [hystrix-metrics-event-stream](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream). + +The [hystrix-dashboard](https://github.com/Netflix/Hystrix/tree/master/hystrix-dashboard) can be used on this example app to monitor its metrics. + +# Run via Gradle + +``` +$ git clone git@github.com:Netflix/Hystrix.git +$ cd Hystrix/hystrix-examples-webapp +$ ../gradlew appRun +> Building > :hystrix-examples-webapp:appRun > Running at http://localhost:8989/hystrix-examples-webapp +``` + +Once running, open http://localhost:8989/hystrix-examples-webapp. + + + diff --git a/Hystrix-master/hystrix-examples-webapp/build.gradle b/Hystrix-master/hystrix-examples-webapp/build.gradle new file mode 100644 index 0000000..9957ee9 --- /dev/null +++ b/Hystrix-master/hystrix-examples-webapp/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'war' +apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin' + +dependencies { + compileApi project(':hystrix-core') + compileApi project(':hystrix-examples') + compileApi project(':hystrix-request-servlet') + compileApi project(':hystrix-metrics-event-stream') +} + +gretty { + httpPort = 8989 + servletContainer = 'jetty9' +} diff --git a/Hystrix-master/hystrix-examples-webapp/src/main/webapp/WEB-INF/classes/log4j.properties b/Hystrix-master/hystrix-examples-webapp/src/main/webapp/WEB-INF/classes/log4j.properties new file mode 100644 index 0000000..91374f6 --- /dev/null +++ b/Hystrix-master/hystrix-examples-webapp/src/main/webapp/WEB-INF/classes/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootLogger=INFO, FILE +log4j.appender.FILE=org.apache.log4j.ConsoleAppender +log4j.appender.FILE.layout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p %C:%L [%C{1}] [%M]: %m%n + +log4j.appender.FILE.httpclient=ERROR diff --git a/Hystrix-master/hystrix-examples-webapp/src/main/webapp/WEB-INF/web.xml b/Hystrix-master/hystrix-examples-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..a189a66 --- /dev/null +++ b/Hystrix-master/hystrix-examples-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,75 @@ + + + + + HystrixRequestContextServletFilter + HystrixRequestContextServletFilter + com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter + + + HystrixRequestContextServletFilter + /* + + + + HystrixRequestLogViaResponseHeaderServletFilter + HystrixRequestLogViaResponseHeaderServletFilter + com.netflix.hystrix.contrib.requestservlet.HystrixRequestLogViaResponseHeaderServletFilter + + + HystrixRequestLogViaResponseHeaderServletFilter + /* + + + + + HystrixMetricsStreamServlet + HystrixMetricsStreamServlet + com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet + + + + HystrixMetricsStreamServlet + /hystrix.stream + + + + + HystrixConfigSseServlet + HystrixConfigSseServlet + com.netflix.hystrix.contrib.sample.stream.HystrixConfigSseServlet + + + + HystrixConfigSseServlet + /hystrix/config.stream + + + + + HystrixUtilizationSseServlet + HystrixUtilizationSseServlet + com.netflix.hystrix.contrib.sample.stream.HystrixUtilizationSseServlet + + + + HystrixUtilizationSseServlet + /hystrix/utilization.stream + + + + + HystrixRequestEventsSseServlet + HystrixRequestEventsSseServlet + com.netflix.hystrix.contrib.requests.stream.HystrixRequestEventsSseServlet + + + + HystrixRequestEventsSseServlet + /hystrix/requests.stream + + + diff --git a/Hystrix-master/hystrix-examples-webapp/src/main/webapp/index.jsp b/Hystrix-master/hystrix-examples-webapp/src/main/webapp/index.jsp new file mode 100644 index 0000000..504af0b --- /dev/null +++ b/Hystrix-master/hystrix-examples-webapp/src/main/webapp/index.jsp @@ -0,0 +1,78 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> + + + + +HystrixCommandDemo Execution and HystrixRequestLog [hystrix-examples-webapp] + + +

+ +

HystrixCommandDemo Execution and HystrixRequestLog
[hystrix-examples-webapp]

+
+

+ The following is the output of HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString() after simulating the execution of several commands. +

+

+ The simulation code is in com.netflix.hystrix.examples.demo.HystrixCommandDemo. +

+ <%@ page import="com.netflix.hystrix.examples.demo.HystrixCommandDemo, com.netflix.hystrix.HystrixRequestLog" %> + <% + new HystrixCommandDemo().executeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment(); + %> +
+ <%= HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString() %> +
+

+ This request log is also part of the HTTP response header with key name "X-HystrixLog". +

+

+ You can view the realtime stream at ./hystrix.stream. +

+

+ To see the realtime stream change over time execute the following (with correct hostname, port etc) to keep accessing the webapp while watching the stream and you will see the metrics change: +

+
+	while true ; do curl "http://localhost:8989/hystrix-examples-webapp"; done
+	
+

+ The configuration of Hystrix for this functionality is done in web.xml as follows: +

+
+	<filter>
+		<display-name>HystrixRequestContextServletFilter</display-name>
+		<filter-name>HystrixRequestContextServletFilter</filter-name>
+		<filter-class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter</filter-class>
+	</filter>
+	<filter-mapping>
+		<filter-name>HystrixRequestContextServletFilter</filter-name>
+		<url-pattern>/*</url-pattern>
+	</filter-mapping>
+	
+	<filter>
+		<display-name>HystrixRequestLogViaResponseHeaderServletFilter</display-name>
+		<filter-name>HystrixRequestLogViaResponseHeaderServletFilter</filter-name>
+		<filter-class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestLogViaResponseHeaderServletFilter</filter-class>
+	</filter>
+	<filter-mapping>
+		<filter-name>HystrixRequestLogViaResponseHeaderServletFilter</filter-name>
+		<url-pattern>/*</url-pattern>
+	</filter-mapping>
+
+	<servlet>
+		<description></description>
+		<display-name>HystrixMetricsStreamServlet</display-name>
+		<servlet-name>HystrixMetricsStreamServlet</servlet-name>
+		<servlet-class>com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet</servlet-class>
+	</servlet>
+
+	<servlet-mapping>
+		<servlet-name>HystrixMetricsStreamServlet</servlet-name>
+		<url-pattern>/hystrix.stream</url-pattern>
+	</servlet-mapping>
+
+ +
+ + \ No newline at end of file diff --git a/Hystrix-master/hystrix-examples/README.md b/Hystrix-master/hystrix-examples/README.md new file mode 100644 index 0000000..2b2b153 --- /dev/null +++ b/Hystrix-master/hystrix-examples/README.md @@ -0,0 +1,66 @@ +## hystrix-examples + +This module contains examples of using [HystrixCommand](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java), [HystrixCollapser](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java) and other aspects of Hystrix. + +## Documentation + +Documentation related to the examples in this module is on the [How To Use](https://github.com/Netflix/Hystrix/wiki/How-To-Use) page. + +## Demo + +To run a [demo app](https://github.com/Netflix/Hystrix/tree/master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java) do the following: + +``` +$ git clone git@github.com:Netflix/Hystrix.git +$ cd Hystrix/ +./gradlew runDemo +``` + +You will see output similar to the following: + +``` +Request => GetUserAccountCommand[SUCCESS][8ms], GetPaymentInformationCommand[SUCCESS][20ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][101ms], CreditCardCommand[SUCCESS][1075ms] +Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][2ms], GetPaymentInformationCommand[SUCCESS][22ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][130ms], CreditCardCommand[SUCCESS][1050ms] +Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][4ms], GetPaymentInformationCommand[SUCCESS][19ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][145ms], CreditCardCommand[SUCCESS][1301ms] +Request => GetUserAccountCommand[SUCCESS][4ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][93ms], CreditCardCommand[SUCCESS][1409ms] + +##################################################################################### +# CreditCardCommand: Requests: 17 Errors: 0 (0%) Mean: 1171 75th: 1391 90th: 1470 99th: 1486 +# GetOrderCommand: Requests: 21 Errors: 0 (0%) Mean: 100 75th: 144 90th: 207 99th: 230 +# GetUserAccountCommand: Requests: 21 Errors: 4 (19%) Mean: 8 75th: 11 90th: 46 99th: 51 +# GetPaymentInformationCommand: Requests: 21 Errors: 0 (0%) Mean: 18 75th: 21 90th: 24 99th: 25 +##################################################################################### + +Request => GetUserAccountCommand[SUCCESS][10ms], GetPaymentInformationCommand[SUCCESS][16ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][51ms], CreditCardCommand[SUCCESS][922ms] +Request => GetUserAccountCommand[SUCCESS][12ms], GetPaymentInformationCommand[SUCCESS][12ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][68ms], CreditCardCommand[SUCCESS][1257ms] +Request => GetUserAccountCommand[SUCCESS][10ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][78ms], CreditCardCommand[SUCCESS][1295ms] +Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][6ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][153ms], CreditCardCommand[SUCCESS][1321ms] +``` + +This demo simulates 4 different [HystrixCommand](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java) implementations with failures, latency, timeouts and duplicate calls in a multi-threaded environment. + +It logs the results of [HystrixRequestLog](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java) and metrics from [HystrixCommandMetrics](https://github.com/Netflix/Hystrix/tree/master/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandMetrics.java). + + + +## Maven Central + +Binaries and dependencies for this module can be found on [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.hystrix%22%20AND%20a%3A%22hystrix-examples%22). + +__GroupId: com.netflix.hystrix__ +__ArtifactId: hystrix-examples__ + +Example for Maven: + +```xml + + com.netflix.hystrix + hystrix-examples + 1.0.2 + +``` +and for Ivy: + +```xml + +``` diff --git a/Hystrix-master/hystrix-examples/build.gradle b/Hystrix-master/hystrix-examples/build.gradle new file mode 100644 index 0000000..57f9288 --- /dev/null +++ b/Hystrix-master/hystrix-examples/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'osgi' + +dependencies { + compileApi project(':hystrix-core') + provided 'junit:junit-dep:4.10' +} + +task(runDemo, dependsOn: 'classes', type: JavaExec) { + main = 'com.netflix.hystrix.examples.demo.HystrixCommandDemo' + classpath = sourceSets.main.runtimeClasspath +} + +jar { + manifest { + name = 'hystrix-examples' + instruction 'Bundle-Vendor', 'Netflix' + instruction 'Bundle-DocURL', 'https://github.com/Netflix/Hystrix' + instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' + instruction 'Eclipse-ExtensibleAPI', 'true' + } +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandCollapserGetValueForKey.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandCollapserGetValueForKey.java new file mode 100644 index 0000000..778c4ac --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandCollapserGetValueForKey.java @@ -0,0 +1,134 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Future; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Sample {@link HystrixCollapser} that automatically batches multiple requests to execute()/queue() + * into a single {@link HystrixCommand} execution for all requests within the defined batch (time or size). + */ +public class CommandCollapserGetValueForKey extends HystrixCollapser, String, Integer> { + + private final Integer key; + + public CommandCollapserGetValueForKey(Integer key) { + this.key = key; + } + + @Override + public Integer getRequestArgument() { + return key; + } + + @Override + protected HystrixCommand> createCommand(final Collection> requests) { + return new BatchCommand(requests); + } + + @Override + protected void mapResponseToRequests(List batchResponse, Collection> requests) { + int count = 0; + for (CollapsedRequest request : requests) { + request.setResponse(batchResponse.get(count++)); + } + } + + private static final class BatchCommand extends HystrixCommand> { + private final Collection> requests; + + private BatchCommand(Collection> requests) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) + .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey"))); + this.requests = requests; + } + + @Override + protected List run() { + ArrayList response = new ArrayList(); + for (CollapsedRequest request : requests) { + // artificial response for each argument received in the batch + response.add("ValueForKey: " + request.getArgument()); + } + return response; + } + } + + public static class UnitTest { + + @Test + public void testCollapser() throws Exception { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + Future f1 = new CommandCollapserGetValueForKey(1).queue(); + Future f2 = new CommandCollapserGetValueForKey(2).queue(); + Future f3 = new CommandCollapserGetValueForKey(3).queue(); + Future f4 = new CommandCollapserGetValueForKey(4).queue(); + + assertEquals("ValueForKey: 1", f1.get()); + assertEquals("ValueForKey: 2", f2.get()); + assertEquals("ValueForKey: 3", f3.get()); + assertEquals("ValueForKey: 4", f4.get()); + + int numExecuted = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size(); + + System.err.println("num executed: " + numExecuted); + + // assert that the batch command 'GetValueForKey' was in fact executed and that it executed only + // once or twice (due to non-determinism of scheduler since this example uses the real timer) + if (numExecuted > 2) { + fail("some of the commands should have been collapsed"); + } + + System.err.println("HystrixRequestLog.getCurrentRequest().getAllExecutedCommands(): " + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands()); + + int numLogs = 0; + for (HystrixInvokableInfo command : HystrixRequestLog.getCurrentRequest().getAllExecutedCommands()) { + numLogs++; + + // assert the command is the one we're expecting + assertEquals("GetValueForKey", command.getCommandKey().name()); + + System.err.println(command.getCommandKey().name() + " => command.getExecutionEvents(): " + command.getExecutionEvents()); + + // confirm that it was a COLLAPSED command execution + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + assertEquals(numExecuted, numLogs); + } finally { + context.shutdown(); + } + } + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandFacadeWithPrimarySecondary.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandFacadeWithPrimarySecondary.java new file mode 100644 index 0000000..42db4b6 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandFacadeWithPrimarySecondary.java @@ -0,0 +1,146 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.netflix.config.ConfigurationManager; +import com.netflix.config.DynamicBooleanProperty; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Sample {@link HystrixCommand} pattern using a semaphore-isolated command + * that conditionally invokes thread-isolated commands. + */ +public class CommandFacadeWithPrimarySecondary extends HystrixCommand { + + private final static DynamicBooleanProperty usePrimary = DynamicPropertyFactory.getInstance().getBooleanProperty("primarySecondary.usePrimary", true); + + private final int id; + + public CommandFacadeWithPrimarySecondary(int id) { + super(Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX")) + .andCommandKey(HystrixCommandKey.Factory.asKey("PrimarySecondaryCommand")) + .andCommandPropertiesDefaults( + // we want to default to semaphore-isolation since this wraps + // 2 others commands that are already thread isolated + HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); + this.id = id; + } + + @Override + protected String run() { + if (usePrimary.get()) { + return new PrimaryCommand(id).execute(); + } else { + return new SecondaryCommand(id).execute(); + } + } + + @Override + protected String getFallback() { + return "static-fallback-" + id; + } + + @Override + protected String getCacheKey() { + return String.valueOf(id); + } + + private static class PrimaryCommand extends HystrixCommand { + + private final int id; + + private PrimaryCommand(int id) { + super(Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX")) + .andCommandKey(HystrixCommandKey.Factory.asKey("PrimaryCommand")) + .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("PrimaryCommand")) + .andCommandPropertiesDefaults( + // we default to a 600ms timeout for primary + HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(600))); + this.id = id; + } + + @Override + protected String run() { + // perform expensive 'primary' service call + return "responseFromPrimary-" + id; + } + + } + + private static class SecondaryCommand extends HystrixCommand { + + private final int id; + + private SecondaryCommand(int id) { + super(Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX")) + .andCommandKey(HystrixCommandKey.Factory.asKey("SecondaryCommand")) + .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("SecondaryCommand")) + .andCommandPropertiesDefaults( + // we default to a 100ms timeout for secondary + HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(100))); + this.id = id; + } + + @Override + protected String run() { + // perform fast 'secondary' service call + return "responseFromSecondary-" + id; + } + + } + + public static class UnitTest { + + @Test + public void testPrimary() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", true); + assertEquals("responseFromPrimary-20", new CommandFacadeWithPrimarySecondary(20).execute()); + } finally { + context.shutdown(); + ConfigurationManager.getConfigInstance().clear(); + } + } + + @Test + public void testSecondary() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", false); + assertEquals("responseFromSecondary-20", new CommandFacadeWithPrimarySecondary(20).execute()); + } finally { + context.shutdown(); + ConfigurationManager.getConfigInstance().clear(); + } + } + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandHelloFailure.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandHelloFailure.java new file mode 100644 index 0000000..009459b --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandHelloFailure.java @@ -0,0 +1,74 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import java.util.concurrent.Future; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * Sample {@link HystrixCommand} showing a basic fallback implementation. + */ +public class CommandHelloFailure extends HystrixCommand { + + private final String name; + + public CommandHelloFailure(String name) { + super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); + this.name = name; + } + + @Override + protected String run() { + throw new RuntimeException("this command always fails"); + } + + @Override + protected String getFallback() { + return "Hello Failure " + name + "!"; + } + + public static class UnitTest { + + @Test + public void testSynchronous() { + assertEquals("Hello Failure World!", new CommandHelloFailure("World").execute()); + assertEquals("Hello Failure Bob!", new CommandHelloFailure("Bob").execute()); + } + + @Test + public void testAsynchronous1() throws Exception { + assertEquals("Hello Failure World!", new CommandHelloFailure("World").queue().get()); + assertEquals("Hello Failure Bob!", new CommandHelloFailure("Bob").queue().get()); + } + + @Test + public void testAsynchronous2() throws Exception { + + Future fWorld = new CommandHelloFailure("World").queue(); + Future fBob = new CommandHelloFailure("Bob").queue(); + + assertEquals("Hello Failure World!", fWorld.get()); + assertEquals("Hello Failure Bob!", fBob.get()); + } + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandHelloWorld.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandHelloWorld.java new file mode 100644 index 0000000..79813e7 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandHelloWorld.java @@ -0,0 +1,135 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import java.util.concurrent.Future; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.functions.Action1; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * The obligatory "Hello World!" showing a simple implementation of a {@link HystrixCommand}. + */ +public class CommandHelloWorld extends HystrixCommand { + + private final String name; + + public CommandHelloWorld(String name) { + super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); + this.name = name; + } + + @Override + protected String run() { + return "Hello " + name + "!"; + } + + public static class UnitTest { + + @Test + public void testSynchronous() { + assertEquals("Hello World!", new CommandHelloWorld("World").execute()); + assertEquals("Hello Bob!", new CommandHelloWorld("Bob").execute()); + } + + @Test + public void testAsynchronous1() throws Exception { + assertEquals("Hello World!", new CommandHelloWorld("World").queue().get()); + assertEquals("Hello Bob!", new CommandHelloWorld("Bob").queue().get()); + } + + @Test + public void testAsynchronous2() throws Exception { + + Future fWorld = new CommandHelloWorld("World").queue(); + Future fBob = new CommandHelloWorld("Bob").queue(); + + assertEquals("Hello World!", fWorld.get()); + assertEquals("Hello Bob!", fBob.get()); + } + + @Test + public void testObservable() throws Exception { + + Observable fWorld = new CommandHelloWorld("World").observe(); + Observable fBob = new CommandHelloWorld("Bob").observe(); + + // blocking + assertEquals("Hello World!", fWorld.toBlocking().single()); + assertEquals("Hello Bob!", fBob.toBlocking().single()); + + // non-blocking + // - this is a verbose anonymous inner-class approach and doesn't do assertions + fWorld.subscribe(new Observer() { + + @Override + public void onCompleted() { + // nothing needed here + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(String v) { + System.out.println("onNext: " + v); + } + + }); + + // non-blocking + // - also verbose anonymous inner-class + // - ignore errors and onCompleted signal + fBob.subscribe(new Action1() { + + @Override + public void call(String v) { + System.out.println("onNext: " + v); + } + + }); + + // non-blocking + // - using closures in Java 8 would look like this: + + // fWorld.subscribe((v) -> { + // System.out.println("onNext: " + v); + // }) + + // - or while also including error handling + + // fWorld.subscribe((v) -> { + // System.out.println("onNext: " + v); + // }, (exception) -> { + // exception.printStackTrace(); + // }) + + // More information about Observable can be found at https://github.com/Netflix/RxJava/wiki/How-To-Use + + } + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandThatFailsFast.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandThatFailsFast.java new file mode 100644 index 0000000..8cf54a4 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandThatFailsFast.java @@ -0,0 +1,66 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.exception.HystrixRuntimeException; + +/** + * Sample {@link HystrixCommand} that does not have a fallback implemented + * so will "fail fast" when failures, rejections, short-circuiting etc occur. + */ +public class CommandThatFailsFast extends HystrixCommand { + + private final boolean throwException; + + public CommandThatFailsFast(boolean throwException) { + super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); + this.throwException = throwException; + } + + @Override + protected String run() { + if (throwException) { + throw new RuntimeException("failure from CommandThatFailsFast"); + } else { + return "success"; + } + } + + public static class UnitTest { + + @Test + public void testSuccess() { + assertEquals("success", new CommandThatFailsFast(false).execute()); + } + + @Test + public void testFailure() { + try { + new CommandThatFailsFast(true).execute(); + fail("we should have thrown an exception"); + } catch (HystrixRuntimeException e) { + assertEquals("failure from CommandThatFailsFast", e.getCause().getMessage()); + e.printStackTrace(); + } + } + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandThatFailsSilently.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandThatFailsSilently.java new file mode 100644 index 0000000..37ed285 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandThatFailsSilently.java @@ -0,0 +1,76 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.exception.HystrixRuntimeException; + +/** + * Sample {@link HystrixCommand} that has a fallback implemented + * that will "fail silent" when failures, rejections, short-circuiting etc occur + * by returning an empty List. + */ +public class CommandThatFailsSilently extends HystrixCommand> { + + private final boolean throwException; + + public CommandThatFailsSilently(boolean throwException) { + super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); + this.throwException = throwException; + } + + @Override + protected List run() { + if (throwException) { + throw new RuntimeException("failure from CommandThatFailsFast"); + } else { + ArrayList values = new ArrayList(); + values.add("success"); + return values; + } + } + + @Override + protected List getFallback() { + return Collections.emptyList(); + } + + public static class UnitTest { + + @Test + public void testSuccess() { + assertEquals("success", new CommandThatFailsSilently(false).execute().get(0)); + } + + @Test + public void testFailure() { + try { + assertEquals(0, new CommandThatFailsSilently(true).execute().size()); + } catch (HystrixRuntimeException e) { + fail("we should not get an exception as we fail silently with a fallback"); + } + } + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingRequestCache.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingRequestCache.java new file mode 100644 index 0000000..fba9583 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingRequestCache.java @@ -0,0 +1,95 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Sample {@link HystrixCommand} showing how implementing the {@link #getCacheKey()} method + * enables request caching for eliminating duplicate calls within the same request context. + */ +public class CommandUsingRequestCache extends HystrixCommand { + + private final int value; + + protected CommandUsingRequestCache(int value) { + super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); + this.value = value; + } + + @Override + protected Boolean run() { + return value == 0 || value % 2 == 0; + } + + @Override + protected String getCacheKey() { + return String.valueOf(value); + } + + public static class UnitTest { + + @Test + public void testWithoutCacheHits() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + assertTrue(new CommandUsingRequestCache(2).execute()); + assertFalse(new CommandUsingRequestCache(1).execute()); + assertTrue(new CommandUsingRequestCache(0).execute()); + assertTrue(new CommandUsingRequestCache(58672).execute()); + } finally { + context.shutdown(); + } + } + + @Test + public void testWithCacheHits() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + CommandUsingRequestCache command2a = new CommandUsingRequestCache(2); + CommandUsingRequestCache command2b = new CommandUsingRequestCache(2); + + assertTrue(command2a.execute()); + // this is the first time we've executed this command with the value of "2" so it should not be from cache + assertFalse(command2a.isResponseFromCache()); + + assertTrue(command2b.execute()); + // this is the second time we've executed this command with the same value so it should return from cache + assertTrue(command2b.isResponseFromCache()); + } finally { + context.shutdown(); + } + + // start a new request context + context = HystrixRequestContext.initializeContext(); + try { + CommandUsingRequestCache command3b = new CommandUsingRequestCache(2); + assertTrue(command3b.execute()); + // this is a new request context so this should not come from cache + assertFalse(command3b.isResponseFromCache()); + } finally { + context.shutdown(); + } + } + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingRequestCacheInvalidation.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingRequestCacheInvalidation.java new file mode 100644 index 0000000..e1c9056 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingRequestCacheInvalidation.java @@ -0,0 +1,118 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixRequestCache; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Example {@link HystrixCommand} implementation for handling the get-set-get use case within + * a single request context so that the "set" can invalidate the cached "get". + */ +public class CommandUsingRequestCacheInvalidation { + + /* represents a remote data store */ + private static volatile String prefixStoredOnRemoteDataStore = "ValueBeforeSet_"; + + public static class GetterCommand extends HystrixCommand { + + private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("GetterCommand"); + private final int id; + + public GetterCommand(int id) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetSetGet")) + .andCommandKey(GETTER_KEY)); + this.id = id; + } + + @Override + protected String run() { + return prefixStoredOnRemoteDataStore + id; + } + + @Override + protected String getCacheKey() { + return String.valueOf(id); + } + + /** + * Allow the cache to be flushed for this object. + * + * @param id + * argument that would normally be passed to the command + */ + public static void flushCache(int id) { + HystrixRequestCache.getInstance(GETTER_KEY, + HystrixConcurrencyStrategyDefault.getInstance()).clear(String.valueOf(id)); + } + + } + + public static class SetterCommand extends HystrixCommand { + + private final int id; + private final String prefix; + + public SetterCommand(int id, String prefix) { + super(HystrixCommandGroupKey.Factory.asKey("GetSetGet")); + this.id = id; + this.prefix = prefix; + } + + @Override + protected Void run() { + // persist the value against the datastore + prefixStoredOnRemoteDataStore = prefix; + // flush the cache + GetterCommand.flushCache(id); + // no return value + return null; + } + } + + public static class UnitTest { + + @Test + public void getGetSetGet() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + assertEquals("ValueBeforeSet_1", new GetterCommand(1).execute()); + GetterCommand commandAgainstCache = new GetterCommand(1); + assertEquals("ValueBeforeSet_1", commandAgainstCache.execute()); + // confirm it executed against cache the second time + assertTrue(commandAgainstCache.isResponseFromCache()); + // set the new value + new SetterCommand(1, "ValueAfterSet_").execute(); + // fetch it again + GetterCommand commandAfterSet = new GetterCommand(1); + // the getter should return with the new prefix, not the value from cache + assertFalse(commandAfterSet.isResponseFromCache()); + assertEquals("ValueAfterSet_1", commandAfterSet.execute()); + } finally { + context.shutdown(); + } + } + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingSemaphoreIsolation.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingSemaphoreIsolation.java new file mode 100644 index 0000000..e4d0cb2 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandUsingSemaphoreIsolation.java @@ -0,0 +1,47 @@ +/** + * Copyright 2012 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.examples.basic; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; + +/** + * Example of {@link HystrixCommand} defaulting to use a semaphore isolation strategy + * when its run() method will not perform network traffic. + */ +public class CommandUsingSemaphoreIsolation extends HystrixCommand { + + private final int id; + + public CommandUsingSemaphoreIsolation(int id) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) + // since we're doing work in the run() method that doesn't involve network traffic + // and executes very fast with low risk we choose SEMAPHORE isolation + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); + this.id = id; + } + + @Override + protected String run() { + // a real implementation would retrieve data from in memory data structure + // or some other similar non-network involved work + return "ValueFromHashMap_" + id; + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandWithFallbackViaNetwork.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandWithFallbackViaNetwork.java new file mode 100644 index 0000000..c9c1d25 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandWithFallbackViaNetwork.java @@ -0,0 +1,109 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Sample {@link HystrixCommand} that implements fallback logic that requires + * network traffic and thus executes another {@link HystrixCommand} from the {@link #getFallback()} method. + *

+ * Note also that the fallback command uses a separate thread-pool as well even though + * it's in the same command group. + *

+ * It needs to be on a separate thread-pool otherwise the first command could saturate it + * and the fallback command never have a chance to execute. + */ +public class CommandWithFallbackViaNetwork extends HystrixCommand { + private final int id; + + protected CommandWithFallbackViaNetwork(int id) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX")) + .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueCommand"))); + this.id = id; + } + + @Override + protected String run() { + // RemoteServiceXClient.getValue(id); + throw new RuntimeException("force failure for example"); + } + + @Override + protected String getFallback() { + return new FallbackViaNetwork(id).execute(); + } + + private static class FallbackViaNetwork extends HystrixCommand { + private final int id; + + public FallbackViaNetwork(int id) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX")) + .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueFallbackCommand")) + // use a different threadpool for the fallback command + // so saturating the RemoteServiceX pool won't prevent + // fallbacks from executing + .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback"))); + this.id = id; + } + + @Override + protected String run() { + // MemCacheClient.getValue(id); + throw new RuntimeException("the fallback also failed"); + } + + @Override + protected String getFallback() { + // the fallback also failed + // so this fallback-of-a-fallback will + // fail silently and return null + return null; + } + } + + public static class UnitTest { + + @Test + public void test() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + assertEquals(null, new CommandWithFallbackViaNetwork(1).execute()); + + HystrixInvokableInfo command1 = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixInvokableInfo[2])[0]; + assertEquals("GetValueCommand", command1.getCommandKey().name()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.FAILURE)); + + HystrixInvokableInfo command2 = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixInvokableInfo[2])[1]; + assertEquals("GetValueFallbackCommand", command2.getCommandKey().name()); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.FAILURE)); + } finally { + context.shutdown(); + } + } + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandWithStubbedFallback.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandWithStubbedFallback.java new file mode 100644 index 0000000..bb1b727 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/CommandWithStubbedFallback.java @@ -0,0 +1,102 @@ +/** + * Copyright 2012 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.examples.basic; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.examples.basic.CommandWithStubbedFallback.UserAccount; + +/** + * Sample {@link HystrixCommand} that implements a fallback that returns an object + * combining defaults and injected values from elsewhere in the system (such as + * HTTP request headers, arguments and cookies or other services previously executed). + */ +public class CommandWithStubbedFallback extends HystrixCommand { + + private final int customerId; + private final String countryCodeFromGeoLookup; + + /** + * @param customerId + * The customerID to retrieve UserAccount for + * @param countryCodeFromGeoLookup + * The default country code from the HTTP request geo code lookup used for fallback. + */ + protected CommandWithStubbedFallback(int customerId, String countryCodeFromGeoLookup) { + super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); + this.customerId = customerId; + this.countryCodeFromGeoLookup = countryCodeFromGeoLookup; + } + + @Override + protected UserAccount run() { + // fetch UserAccount from remote service + // return UserAccountClient.getAccount(customerId); + throw new RuntimeException("forcing failure for example"); + } + + @Override + protected UserAccount getFallback() { + /** + * Return stubbed fallback with some static defaults, placeholders, + * and an injected value 'countryCodeFromGeoLookup' that we'll use + * instead of what we would have retrieved from the remote service. + */ + return new UserAccount(customerId, "Unknown Name", + countryCodeFromGeoLookup, true, true, false); + } + + public static class UserAccount { + private final int customerId; + private final String name; + private final String countryCode; + private final boolean isFeatureXPermitted; + private final boolean isFeatureYPermitted; + private final boolean isFeatureZPermitted; + + UserAccount(int customerId, String name, String countryCode, + boolean isFeatureXPermitted, + boolean isFeatureYPermitted, + boolean isFeatureZPermitted) { + this.customerId = customerId; + this.name = name; + this.countryCode = countryCode; + this.isFeatureXPermitted = isFeatureXPermitted; + this.isFeatureYPermitted = isFeatureYPermitted; + this.isFeatureZPermitted = isFeatureZPermitted; + } + } + + public static class UnitTest { + + @Test + public void test() { + CommandWithStubbedFallback command = new CommandWithStubbedFallback(1234, "ca"); + UserAccount account = command.execute(); + assertTrue(command.isFailedExecution()); + assertTrue(command.isResponseFromFallback()); + assertEquals(1234, account.customerId); + assertEquals("ca", account.countryCode); + assertEquals(true, account.isFeatureXPermitted); + assertEquals(true, account.isFeatureYPermitted); + assertEquals(false, account.isFeatureZPermitted); + } + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/ObservableCollapserGetWordForNumber.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/ObservableCollapserGetWordForNumber.java new file mode 100644 index 0000000..d031787 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/ObservableCollapserGetWordForNumber.java @@ -0,0 +1,318 @@ +/** + * Copyright 2016 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.examples.basic; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import rx.Observable; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; +import com.netflix.hystrix.HystrixObservableCollapser; +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.examples.basic.ObservableCommandNumbersToWords.NumberWord; +import com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Example that uses {@link HystrixObservableCollapser} to batch multiple {@link ObservableCommandNumbersToWords} requests. + * + * @author Patrick Ruhkopf + */ +public class ObservableCollapserGetWordForNumber extends HystrixObservableCollapser +{ + private final Integer number; + + private final static AtomicInteger counter = new AtomicInteger(); + + public static void resetCmdCounter() + { + counter.set(0); + } + + public static int getCmdCount() + { + return counter.get(); + } + + public ObservableCollapserGetWordForNumber(final Integer number) + { + this.number = number; + } + + @Override + public Integer getRequestArgument() + { + return number; + } + + @SuppressWarnings("boxing") + @Override + protected HystrixObservableCommand createCommand(final Collection> requests) + { + final int count = counter.incrementAndGet(); + System.out.println("Creating batch for " + requests.size() + " requests. Total invocations so far: " + count); + + final List numbers = new ArrayList(); + for (final CollapsedRequest request : requests) + { + numbers.add(request.getArgument()); + } + + return new ObservableCommandNumbersToWords(numbers); + } + + @Override + protected Func1 getBatchReturnTypeKeySelector() + { + // Java 8: (final NumberWord nw) -> nw.getNumber(); + + return new Func1() + { + @Override + public Integer call(final NumberWord nw) + { + return nw.getNumber(); + } + }; + } + + @Override + protected Func1 getRequestArgumentKeySelector() + { + // Java 8: return (final Integer no) -> no; + + return new Func1() + { + @Override + public Integer call(final Integer no) + { + return no; + } + + }; + } + + @Override + protected Func1 getBatchReturnTypeToResponseTypeMapper() + { + // Java 8: return (final NumberWord nw) -> nw.getWord(); + + return new Func1() + { + @Override + public String call(final NumberWord nw) + { + return nw.getWord(); + } + }; + } + + @Override + protected void onMissingResponse(final CollapsedRequest request) + { + request.setException(new Exception("No word")); + } + + public static class ObservableCollapserGetWordForNumberTest + { + private HystrixRequestContext ctx; + + @Before + public void before() + { + ctx = HystrixRequestContext.initializeContext(); + ObservableCollapserGetWordForNumber.resetCmdCounter(); + } + + @After + public void after() + { + System.out.println(HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + ctx.shutdown(); + } + + /** + * Example where we subscribe without using a specific scheduler. That means we run the actions on the same thread. + */ + @Test + public void shouldCollapseRequestsSync() + { + final int noOfRequests = 10; + final Map> subscribersByNumber = new HashMap>( + noOfRequests); + + TestSubscriber subscriber; + for (int number = 0; number < noOfRequests; number++) + { + subscriber = new TestSubscriber(); + new ObservableCollapserGetWordForNumber(number).toObservable().subscribe(subscriber); + subscribersByNumber.put(number, subscriber); + + // wait a little bit after running half of the requests so that we don't collapse all of them into one batch + // TODO this can probably be improved by using a test scheduler + if (number == noOfRequests / 2) + sleep(1000); + + } + + assertThat(subscribersByNumber.size(), is(noOfRequests)); + for (final Entry> subscriberByNumber : subscribersByNumber.entrySet()) + { + subscriber = subscriberByNumber.getValue(); + subscriber.awaitTerminalEvent(10, TimeUnit.SECONDS); + + assertThat(subscriber.getOnErrorEvents().toString(), subscriber.getOnErrorEvents().size(), is(0)); + assertThat(subscriber.getOnNextEvents().size(), is(1)); + + final String word = subscriber.getOnNextEvents().get(0); + System.out.println("Translated " + subscriberByNumber.getKey() + " to " + word); + assertThat(word, equalTo(numberToWord(subscriberByNumber.getKey()))); + } + + assertTrue(ObservableCollapserGetWordForNumber.getCmdCount() > 1); + assertTrue(ObservableCollapserGetWordForNumber.getCmdCount() < noOfRequests); + } + + /** + * Example where we subscribe on the computation scheduler. For this we need the {@link HystrixContextScheduler}, that + * passes the {@link HystrixRequestContext} to the thread that runs the action. + */ + @Test + public void shouldCollapseRequestsAsync() + { + final HystrixContextScheduler contextAwareScheduler = new HystrixContextScheduler(Schedulers.computation()); + + final int noOfRequests = 10; + final Map> subscribersByNumber = new HashMap>( + noOfRequests); + + TestSubscriber subscriber; + for (int number = 0; number < noOfRequests; number++) + { + subscriber = new TestSubscriber(); + final int finalNumber = number; + + // defer and subscribe on specific scheduler + Observable.defer(new Func0>() + { + @Override + public Observable call() + { + return new ObservableCollapserGetWordForNumber(finalNumber).toObservable(); + } + }).subscribeOn(contextAwareScheduler).subscribe(subscriber); + + subscribersByNumber.put(number, subscriber); + + // wait a little bit after running half of the requests so that we don't collapse all of them into one batch + // TODO this can probably be improved by using a test scheduler + if (number == noOfRequests / 2) + sleep(1000); + } + + assertThat(subscribersByNumber.size(), is(noOfRequests)); + for (final Entry> subscriberByNumber : subscribersByNumber.entrySet()) + { + subscriber = subscriberByNumber.getValue(); + subscriber.awaitTerminalEvent(10, TimeUnit.SECONDS); + + assertThat(subscriber.getOnErrorEvents().toString(), subscriber.getOnErrorEvents().size(), is(0)); + assertThat(subscriber.getOnNextEvents().size(), is(1)); + + final String word = subscriber.getOnNextEvents().get(0); + System.out.println("Translated " + subscriberByNumber.getKey() + " to " + word); + assertThat(word, equalTo(numberToWord(subscriberByNumber.getKey()))); + } + + assertTrue(ObservableCollapserGetWordForNumber.getCmdCount() > 1); + assertTrue(ObservableCollapserGetWordForNumber.getCmdCount() < noOfRequests); + } + + @Test + public void shouldCollapseSameRequests() + { + //given + final HystrixContextScheduler contextAwareScheduler = new HystrixContextScheduler(Schedulers.computation()); + + //when + TestSubscriber subscriber1 = getWordForNumber(contextAwareScheduler, 0); + TestSubscriber subscriber2 = getWordForNumber(contextAwareScheduler, 0); + + //then + subscriberReceived(subscriber1, 0); + subscriberReceived(subscriber2, 0); + } + + private TestSubscriber getWordForNumber(HystrixContextScheduler contextAwareScheduler, final int number) { + final TestSubscriber subscriber = new TestSubscriber(); + Observable.defer(new Func0>() + { + @Override + public Observable call() + { + return new ObservableCollapserGetWordForNumber(number).toObservable(); + } + }).subscribeOn(contextAwareScheduler).subscribe(subscriber); + return subscriber; + } + + private void subscriberReceived(TestSubscriber subscriber, int number) { + subscriber.awaitTerminalEvent(10, TimeUnit.SECONDS); + assertThat(subscriber.getOnErrorEvents().toString(), subscriber.getOnErrorEvents().size(), is(0)); + assertThat(subscriber.getOnNextEvents().size(), is(1)); + assertThat(subscriber.getOnNextEvents().get(0), equalTo(numberToWord(number))); + } + + private String numberToWord(final int number) + { + return ObservableCommandNumbersToWords.dict.get(number); + } + + private void sleep(final long ms) + { + try + { + Thread.sleep(1000); + } + catch (final InterruptedException e) + { + throw new IllegalStateException(e); + } + } + + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/ObservableCommandNumbersToWords.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/ObservableCommandNumbersToWords.java new file mode 100644 index 0000000..152b9c4 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/basic/ObservableCommandNumbersToWords.java @@ -0,0 +1,96 @@ +/** + * Copyright 2016 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.examples.basic; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import rx.Observable; +import rx.functions.Func1; + +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.examples.basic.ObservableCommandNumbersToWords.NumberWord; + +/** + * A simple Hystrix Observable command that translates a number (Integer) into an English text. + */ +class ObservableCommandNumbersToWords extends HystrixObservableCommand +{ + private final List numbers; + + // in the real world you'd probably want to replace this very simple code by using ICU or similar + static Map dict = new HashMap(11); + static + { + dict.put(0, "zero"); + dict.put(1, "one"); + dict.put(2, "two"); + dict.put(3, "three"); + dict.put(4, "four"); + dict.put(5, "five"); + dict.put(6, "six"); + dict.put(7, "seven"); + dict.put(8, "eight"); + dict.put(9, "nine"); + dict.put(10, "ten"); + } + + public ObservableCommandNumbersToWords(final List numbers) + { + super(HystrixCommandGroupKey.Factory.asKey(ObservableCommandNumbersToWords.class.getName())); + this.numbers = numbers; + } + + @Override + protected Observable construct() + { + return Observable.from(numbers).map(new Func1() + { + @Override + public NumberWord call(final Integer number) + { + return new NumberWord(number, dict.get(number)); + } + + }); + } + + static class NumberWord + { + private final Integer number; + private final String word; + + public NumberWord(final Integer number, final String word) + { + super(); + this.number = number; + this.word = word; + } + + public Integer getNumber() + { + return number; + } + + public String getWord() + { + return word; + } + } + +} \ No newline at end of file diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardAuthorizationResult.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardAuthorizationResult.java new file mode 100644 index 0000000..956393d --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardAuthorizationResult.java @@ -0,0 +1,110 @@ +/** + * Copyright 2012 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.examples.demo; + +/** + * POJO for holding the result of a CreditCardAuthorization + */ +public class CreditCardAuthorizationResult { + + public static CreditCardAuthorizationResult createSuccessResponse(String transactionID, String authorizationCode) { + return new CreditCardAuthorizationResult(true, transactionID, authorizationCode, false); + } + + public static CreditCardAuthorizationResult createDuplicateSuccessResponse(String transactionID, String authorizationCode) { + return new CreditCardAuthorizationResult(true, transactionID, authorizationCode, true); + } + + public static CreditCardAuthorizationResult createFailedResponse(String message) { + return new CreditCardAuthorizationResult(false, message, null, false); + } + + private final boolean success; + private final boolean isDuplicate; + private final String authorizationCode; + private final String transactionID; + private final String errorMessage; + + /** + * Private constructor that normally would be a horrible API as it re-uses different arguments for different state. + * + * @param success + * @param value + * @param isResponseDuplicate + * boolean whether the response is the result of a duplicate transaction returning a previously submitted transaction result + *

+ * This is for handling the idempotent double-posting scenario, such as retries after timeouts. + */ + private CreditCardAuthorizationResult(boolean success, String value, String value2, boolean isResponseDuplicate) { + this.success = success; + this.isDuplicate = isResponseDuplicate; + if (success) { + this.transactionID = value; + this.authorizationCode = value2; + this.errorMessage = null; + } else { + this.transactionID = null; + this.errorMessage = value; + this.authorizationCode = null; + } + } + + public boolean isSuccess() { + return success; + } + + /** + * Whether this result was a duplicate transaction. + * + * @return boolean + */ + public boolean isDuplicateTransaction() { + return isDuplicate; + } + + /** + * If isSuccess() == true this will return the authorization code. + *

+ * If isSuccess() == false this will return NULL. + * + * @return String + */ + public String getAuthorizationCode() { + return authorizationCode; + } + + /** + * If isSuccess() == true this will return the transaction ID. + *

+ * If isSuccess() == false this will return NULL. + * + * @return String + */ + public String getTransactionID() { + return transactionID; + } + + /** + * If isSuccess() == false this will return the error message. + *

+ * If isSuccess() == true this will return NULL. + * + * @return String + */ + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardCommand.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardCommand.java new file mode 100644 index 0000000..45ef483 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardCommand.java @@ -0,0 +1,237 @@ +/** + * Copyright 2012 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.examples.demo; + +import java.math.BigDecimal; +import java.net.HttpCookie; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandProperties; + +/** + * This class was originally taken from a functional example using the Authorize.net API + * but was modified for this example to use mock classes so that the real API does not need + * to be depended upon and so that a backend account with Authorize.net is not needed. + */ +// import net.authorize.Environment; +// import net.authorize.TransactionType; +// import net.authorize.aim.Result; +// import net.authorize.aim.Transaction; + +/** + * HystrixCommand for submitting credit card payments. + *

+ * No fallback implemented as a credit card failure must result in an error as no logical fallback exists. + *

+ * This implementation originated from a functional HystrixCommand wrapper around an Authorize.net API. + *

+ * The original used the Authorize.net 'duplicate window' setting to ensure an Order could be submitted multiple times + * and it would behave idempotently so that it would not result in duplicate transactions and each would return a successful + * response as if it was the first-and-only execution. + *

+ * This idempotence (within the duplicate window time frame set to multiple hours) allows for clients that + * experience timeouts and failures to confidently retry the credit card transaction without fear of duplicate + * credit card charges. + *

+ * This in turn allows the HystrixCommand to be configured for reasonable timeouts and isolation rather than + * letting it go 10+ seconds hoping for success when latency occurs. + *

+ * In this example, the timeout is set to 3,000ms as normal behavior typically saw a credit card transaction taking around 1300ms + * and in this case it's better to wait longer and try to succeed as the result is a user error. + *

+ * We do not want to wait the 10,000-20,000ms that Authorize.net can default to as that would allow severe resource + * saturation under high volume traffic when latency spikes. + */ +public class CreditCardCommand extends HystrixCommand { + private final static AuthorizeNetGateway DEFAULT_GATEWAY = new AuthorizeNetGateway(); + + private final AuthorizeNetGateway gateway; + private final Order order; + private final PaymentInformation payment; + private final BigDecimal amount; + + /** + * A HystrixCommand implementation accepts arguments into the constructor which are then accessible + * to the run() method when it executes. + * + * @param order + * @param payment + * @param amount + */ + public CreditCardCommand(Order order, PaymentInformation payment, BigDecimal amount) { + this(DEFAULT_GATEWAY, order, payment, amount); + } + + private CreditCardCommand(AuthorizeNetGateway gateway, Order order, PaymentInformation payment, BigDecimal amount) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CreditCard")) + // defaulting to a fairly long timeout value because failing a credit card transaction is a bad user experience and 'costly' to re-attempt + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(3000))); + this.gateway = gateway; + this.order = order; + this.payment = payment; + this.amount = amount; + } + + /** + * Actual work of submitting the credit card authorization occurs within this HystrixCommand.run() method. + */ + @Override + protected CreditCardAuthorizationResult run() { + // Simulate transitive dependency from CreditCardCommand to GetUserAccountCommand. + // UserAccount could be injected into this command as an argument (and that would be more accurate) + // but often in large codebase that ends up not happening and each library fetches common data + // such as user information directly such as this example. + UserAccount user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).execute(); + if (user.getAccountType() == 1) { + // do something + } else { + // do something else + } + + // perform credit card transaction + Result result = gateway.submit(payment.getCreditCardNumber(), + String.valueOf(payment.getExpirationMonth()), + String.valueOf(payment.getExpirationYear()), + TransactionType.AUTH_CAPTURE, amount, order); + + if (result.isApproved()) { + return CreditCardAuthorizationResult.createSuccessResponse(result.getTarget().getTransactionId(), result.getTarget().getAuthorizationCode()); + } else if (result.isDeclined()) { + return CreditCardAuthorizationResult.createFailedResponse(result.getReasonResponseCode() + " : " + result.getResponseText()); + } else { + // check for duplicate transaction + if (result.getReasonResponseCode().getResponseReasonCode() == 11) { + if (result.getTarget().getAuthorizationCode() != null) { + // We will treat this as a success as this is telling us we have a successful authorization code + // just that we attempted to re-post it again during the 'duplicateWindow' time period. + // This is part of the idempotent behavior we require so that we can safely timeout and/or fail and allow + // client applications to re-attempt submitting a credit card transaction for the same order again. + // In those cases if the client saw a failure but the transaction actually succeeded, this will capture the + // duplicate response and behave to the client as a success. + return CreditCardAuthorizationResult.createDuplicateSuccessResponse(result.getTarget().getTransactionId(), result.getTarget().getAuthorizationCode()); + } + } + // handle all other errors + return CreditCardAuthorizationResult.createFailedResponse(result.getReasonResponseCode() + " : " + result.getResponseText()); + /** + * NOTE that in this use case we do not throw an exception for an "error" as this type of error from the service is not a system error, + * but a legitimate usage problem successfully delivered back from the service. + * + * Unexpected errors will be allowed to throw RuntimeExceptions. + * + * The HystrixBadRequestException could potentially be used here, but with such a complex set of errors and reason codes + * it was chosen to stick with the response object approach rather than using an exception. + */ + } + } + + /* + * The following inner classes are all mocks based on the Authorize.net API that this class originally used. + * + * They are statically mocked in this example to demonstrate how Hystrix might behave when wrapping this type of call. + */ + + public static class AuthorizeNetGateway { + public AuthorizeNetGateway() { + + } + + public Result submit(String creditCardNumber, String expirationMonth, String expirationYear, TransactionType authCapture, BigDecimal amount, Order order) { + /* simulate varying length of time 800-1500ms which is typical for a credit card transaction */ + try { + Thread.sleep((int) (Math.random() * 700) + 800); + } catch (InterruptedException e) { + // do nothing + } + + /* and every once in a while we'll cause it to go longer than 3000ms which will cause the command to timeout */ + if (Math.random() > 0.99) { + try { + Thread.sleep(8000); + } catch (InterruptedException e) { + // do nothing + } + } + + if (Math.random() < 0.8) { + return new Result(true); + } else { + return new Result(false); + } + + } + } + + public static class Result { + + private final boolean approved; + + public Result(boolean approved) { + this.approved = approved; + } + + public boolean isApproved() { + return approved; + } + + public ResponseCode getResponseText() { + return null; + } + + public Target getTarget() { + return new Target(); + } + + public ResponseCode getReasonResponseCode() { + return new ResponseCode(); + } + + public boolean isDeclined() { + return !approved; + } + + } + + public static class ResponseCode { + + public int getResponseReasonCode() { + return 0; + } + + } + + public static class Target { + + public String getTransactionId() { + return "transactionId"; + } + + public String getAuthorizationCode() { + return "authorizedCode"; + } + + } + + public static class Transaction { + + } + + public static enum TransactionType { + AUTH_CAPTURE + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetOrderCommand.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetOrderCommand.java new file mode 100644 index 0000000..3802a41 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetOrderCommand.java @@ -0,0 +1,63 @@ +/** + * Copyright 2012 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.examples.demo; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * Sample HystrixCommand simulating one that would fetch Order objects from a remote service or database. + *

+ * This fails fast with no fallback and does not use request caching. + */ +public class GetOrderCommand extends HystrixCommand { + + private final int orderId; + + public GetOrderCommand(int orderId) { + super(HystrixCommandGroupKey.Factory.asKey("Order")); + this.orderId = orderId; + } + + @Override + protected Order run() { + /* simulate performing network call to retrieve order */ + try { + Thread.sleep((int) (Math.random() * 200) + 50); + } catch (InterruptedException e) { + // do nothing + } + + /* fail rarely ... but allow failure as this one has no fallback */ + if (Math.random() > 0.9999) { + throw new RuntimeException("random failure loading order over network"); + } + + /* latency spike 5% of the time */ + if (Math.random() > 0.95) { + // random latency spike + try { + Thread.sleep((int) (Math.random() * 300) + 25); + } catch (InterruptedException e) { + // do nothing + } + } + + /* success ... create Order with data "from" the remote service response */ + return new Order(orderId); + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetPaymentInformationCommand.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetPaymentInformationCommand.java new file mode 100644 index 0000000..e3c03ae --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetPaymentInformationCommand.java @@ -0,0 +1,63 @@ +/** + * Copyright 2012 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.examples.demo; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * Sample HystrixCommand simulating one that would fetch PaymentInformation objects from a remote service or database. + *

+ * This fails fast with no fallback and does not use request caching. + */ +public class GetPaymentInformationCommand extends HystrixCommand { + + private final UserAccount user; + + public GetPaymentInformationCommand(UserAccount user) { + super(HystrixCommandGroupKey.Factory.asKey("PaymentInformation")); + this.user = user; + } + + @Override + protected PaymentInformation run() { + /* simulate performing network call to retrieve order */ + try { + Thread.sleep((int) (Math.random() * 20) + 5); + } catch (InterruptedException e) { + // do nothing + } + + /* fail rarely ... but allow failure */ + if (Math.random() > 0.9999) { + throw new RuntimeException("random failure loading payment information over network"); + } + + /* latency spike 2% of the time */ + if (Math.random() > 0.98) { + // random latency spike + try { + Thread.sleep((int) (Math.random() * 100) + 25); + } catch (InterruptedException e) { + // do nothing + } + } + + /* success ... create (a very insecure) PaymentInformation with data "from" the remote service response */ + return new PaymentInformation(user, "4444888833337777", 12, 15); + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetUserAccountCommand.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetUserAccountCommand.java new file mode 100644 index 0000000..1f532e0 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetUserAccountCommand.java @@ -0,0 +1,131 @@ +/** + * Copyright 2012 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.examples.demo; + +import java.net.HttpCookie; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * Sample HystrixCommand simulating one that would fetch UserAccount objects from a remote service or database. + *

+ * This uses request caching and fallback behavior. + */ +public class GetUserAccountCommand extends HystrixCommand { + + private final HttpCookie httpCookie; + private final UserCookie userCookie; + + /** + * + * @param cookie + * @throws IllegalArgumentException + * if cookie is invalid meaning the user is not authenticated + */ + public GetUserAccountCommand(HttpCookie cookie) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("User"))); + this.httpCookie = cookie; + /* parse or throw an IllegalArgumentException */ + this.userCookie = UserCookie.parseCookie(httpCookie); + } + + @Override + protected UserAccount run() { + /* simulate performing network call to retrieve user information */ + try { + Thread.sleep((int) (Math.random() * 10) + 2); + } catch (InterruptedException e) { + // do nothing + } + + /* fail 5% of the time to show how fallback works */ + if (Math.random() > 0.95) { + throw new RuntimeException("random failure processing UserAccount network response"); + } + + /* latency spike 5% of the time so timeouts can be triggered occasionally */ + if (Math.random() > 0.95) { + // random latency spike + try { + Thread.sleep((int) (Math.random() * 300) + 25); + } catch (InterruptedException e) { + // do nothing + } + } + + /* success ... create UserAccount with data "from" the remote service response */ + return new UserAccount(86975, "John James", 2, true, false, true); + } + + /** + * Use the HttpCookie value as the cacheKey so multiple executions + * in the same HystrixRequestContext will respond from cache. + */ + @Override + protected String getCacheKey() { + return httpCookie.getValue(); + } + + /** + * Fallback that will use data from the UserCookie and stubbed defaults + * to create a UserAccount if the network call failed. + */ + @Override + protected UserAccount getFallback() { + /* + * first 3 come from the HttpCookie + * next 3 are stubbed defaults + */ + return new UserAccount(userCookie.userId, userCookie.name, userCookie.accountType, true, true, true); + } + + /** + * Represents values containing in the cookie. + *

+ * A real version of this could handle decrypting a secure HTTPS cookie. + */ + private static class UserCookie { + /** + * Parse an HttpCookie into a UserCookie or IllegalArgumentException if invalid cookie + * + * @param cookie + * @return UserCookie + * @throws IllegalArgumentException + * if cookie is invalid + */ + private static UserCookie parseCookie(HttpCookie cookie) { + /* real code would parse the cookie here */ + if (Math.random() < 0.998) { + /* valid cookie */ + return new UserCookie(12345, "Henry Peter", 1); + } else { + /* invalid cookie */ + throw new IllegalArgumentException(); + } + } + + public UserCookie(int userId, String name, int accountType) { + this.userId = userId; + this.name = name; + this.accountType = accountType; + } + + private final int userId; + private final String name; + private final int accountType; + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandAsyncDemo.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandAsyncDemo.java new file mode 100644 index 0000000..de228b1 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandAsyncDemo.java @@ -0,0 +1,235 @@ +/** + * Copyright 2012 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.examples.demo; + +import java.math.BigDecimal; +import java.net.HttpCookie; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import rx.Observable; +import rx.Subscriber; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.plugins.RxJavaPlugins; +import rx.plugins.RxJavaSchedulersHook; + +/** + * Executable client that demonstrates the lifecycle, metrics, request log and behavior of HystrixCommands. + */ +public class HystrixCommandAsyncDemo { + +// public static void main(String args[]) { +// new HystrixCommandAsyncDemo().startDemo(true); +// } + + static class ContextAwareRxSchedulersHook extends RxJavaSchedulersHook { + @Override + public Action0 onSchedule(final Action0 initialAction) { + final Runnable initialRunnable = new Runnable() { + @Override + public void run() { + initialAction.call(); + } + }; + final Runnable wrappedRunnable = + new HystrixContextRunnable(initialRunnable); + return new Action0() { + @Override + public void call() { + wrappedRunnable.run(); + } + }; + } + } + + public HystrixCommandAsyncDemo() { + /* + * Instead of using injected properties we'll set them via Archaius + * so the rest of the code behaves as it would in a real system + * where it picks up properties externally provided. + */ + ConfigurationManager.getConfigInstance().setProperty("hystrix.threadpool.default.coreSize", 8); + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.CreditCardCommand.execution.isolation.thread.timeoutInMilliseconds", 3000); + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.GetUserAccountCommand.execution.isolation.thread.timeoutInMilliseconds", 50); + // set the rolling percentile more granular so we see data change every second rather than every 10 seconds as is the default + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.metrics.rollingPercentile.numBuckets", 60); + + RxJavaPlugins.getInstance().registerSchedulersHook(new ContextAwareRxSchedulersHook()); + } + + public void startDemo(final boolean shouldLog) { + startMetricsMonitor(shouldLog); + while (true) { + final HystrixRequestContext context = HystrixRequestContext.initializeContext(); + Observable o = observeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment(); + + final CountDownLatch latch = new CountDownLatch(1); + o.subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + context.shutdown(); + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + latch.countDown(); + context.shutdown(); + } + + @Override + public void onNext(CreditCardAuthorizationResult creditCardAuthorizationResult) { + if (shouldLog) { + System.out.println("Request => " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + } + } + }); + + try { + latch.await(5000, TimeUnit.MILLISECONDS); + } catch (InterruptedException ex) { + System.out.println("INTERRUPTED!"); + } + } + } + + private final static Random r = new Random(); + + private class Pair { + private final A a; + private final B b; + + Pair(A a, B b) { + this.a = a; + this.b = b; + } + + A a() { + return this.a; + } + + B b() { + return this.b; + } + } + + public Observable observeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment() { + /* fetch user object with http cookies */ + try { + Observable user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).observe(); + /* fetch the payment information (asynchronously) for the user so the credit card payment can proceed */ + Observable paymentInformation = user.flatMap(new Func1>() { + @Override + public Observable call(UserAccount userAccount) { + return new GetPaymentInformationCommand(userAccount).observe(); + } + }); + + /* fetch the order we're processing for the user */ + int orderIdFromRequestArgument = 13579; + final Observable previouslySavedOrder = new GetOrderCommand(orderIdFromRequestArgument).observe(); + + return Observable.zip(paymentInformation, previouslySavedOrder, new Func2>() { + @Override + public Pair call(PaymentInformation paymentInformation, Order order) { + return new Pair(paymentInformation, order); + } + }).flatMap(new Func1, Observable>() { + @Override + public Observable call(Pair pair) { + return new CreditCardCommand(pair.b(), pair.a(), new BigDecimal(123.45)).observe(); + } + }); + } catch (IllegalArgumentException ex) { + return Observable.error(ex); + } + + + } + + public void startMetricsMonitor(final boolean shouldLog) { + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + while (true) { + /** + * Since this is a simple example and we know the exact HystrixCommandKeys we are interested in + * we will retrieve the HystrixCommandMetrics objects directly. + * + * Typically you would instead retrieve metrics from where they are published which is by default + * done using Servo: https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring + */ + + // wait 5 seconds on each loop + try { + Thread.sleep(5000); + } catch (Exception e) { + // ignore + } + + // we are using default names so can use class.getSimpleName() to derive the keys + HystrixCommandMetrics creditCardMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(CreditCardCommand.class.getSimpleName())); + HystrixCommandMetrics orderMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetOrderCommand.class.getSimpleName())); + HystrixCommandMetrics userAccountMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetUserAccountCommand.class.getSimpleName())); + HystrixCommandMetrics paymentInformationMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetPaymentInformationCommand.class.getSimpleName())); + + if (shouldLog) { + // print out metrics + StringBuilder out = new StringBuilder(); + out.append("\n"); + out.append("#####################################################################################").append("\n"); + out.append("# CreditCardCommand: " + getStatsStringFromMetrics(creditCardMetrics)).append("\n"); + out.append("# GetOrderCommand: " + getStatsStringFromMetrics(orderMetrics)).append("\n"); + out.append("# GetUserAccountCommand: " + getStatsStringFromMetrics(userAccountMetrics)).append("\n"); + out.append("# GetPaymentInformationCommand: " + getStatsStringFromMetrics(paymentInformationMetrics)).append("\n"); + out.append("#####################################################################################").append("\n"); + System.out.println(out.toString()); + } + } + } + + private String getStatsStringFromMetrics(HystrixCommandMetrics metrics) { + StringBuilder m = new StringBuilder(); + if (metrics != null) { + HealthCounts health = metrics.getHealthCounts(); + m.append("Requests: ").append(health.getTotalRequests()).append(" "); + m.append("Errors: ").append(health.getErrorCount()).append(" (").append(health.getErrorPercentage()).append("%) "); + m.append("Mean: ").append(metrics.getExecutionTimePercentile(50)).append(" "); + m.append("75th: ").append(metrics.getExecutionTimePercentile(75)).append(" "); + m.append("90th: ").append(metrics.getExecutionTimePercentile(90)).append(" "); + m.append("99th: ").append(metrics.getExecutionTimePercentile(99)).append(" "); + } + return m.toString(); + } + + }); + t.setDaemon(true); + t.start(); + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java new file mode 100644 index 0000000..c4a25f7 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java @@ -0,0 +1,162 @@ +/** + * Copyright 2012 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.examples.demo; + +import java.math.BigDecimal; +import java.net.HttpCookie; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Executable client that demonstrates the lifecycle, metrics, request log and behavior of HystrixCommands. + */ +public class HystrixCommandDemo { + + public static void main(String args[]) { + new HystrixCommandDemo().startDemo(); + } + + public HystrixCommandDemo() { + /* + * Instead of using injected properties we'll set them via Archaius + * so the rest of the code behaves as it would in a real system + * where it picks up properties externally provided. + */ + ConfigurationManager.getConfigInstance().setProperty("hystrix.threadpool.default.coreSize", 8); + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.CreditCardCommand.execution.isolation.thread.timeoutInMilliseconds", 3000); + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.GetUserAccountCommand.execution.isolation.thread.timeoutInMilliseconds", 50); + // set the rolling percentile more granular so we see data change every second rather than every 10 seconds as is the default + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.metrics.rollingPercentile.numBuckets", 60); + } + + /* + * Thread-pool to simulate HTTP requests. + * + * Use CallerRunsPolicy so we can just keep iterating and adding to it and it will block when full. + */ + private final ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 5, TimeUnit.DAYS, new SynchronousQueue(), new ThreadPoolExecutor.CallerRunsPolicy()); + + public void startDemo() { + startMetricsMonitor(); + while (true) { + runSimulatedRequestOnThread(); + } + } + + public void runSimulatedRequestOnThread() { + pool.execute(new Runnable() { + + @Override + public void run() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + executeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment(); + + System.out.println("Request => " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + context.shutdown(); + } + } + + }); + } + + public void executeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment() throws InterruptedException, ExecutionException { + /* fetch user object with http cookies */ + UserAccount user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).execute(); + + /* fetch the payment information (asynchronously) for the user so the credit card payment can proceed */ + Future paymentInformation = new GetPaymentInformationCommand(user).queue(); + + /* fetch the order we're processing for the user */ + int orderIdFromRequestArgument = 13579; + Order previouslySavedOrder = new GetOrderCommand(orderIdFromRequestArgument).execute(); + + CreditCardCommand credit = new CreditCardCommand(previouslySavedOrder, paymentInformation.get(), new BigDecimal(123.45)); + credit.execute(); + } + + public void startMetricsMonitor() { + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + while (true) { + /** + * Since this is a simple example and we know the exact HystrixCommandKeys we are interested in + * we will retrieve the HystrixCommandMetrics objects directly. + * + * Typically you would instead retrieve metrics from where they are published which is by default + * done using Servo: https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring + */ + + // wait 5 seconds on each loop + try { + Thread.sleep(5000); + } catch (Exception e) { + // ignore + } + + // we are using default names so can use class.getSimpleName() to derive the keys + HystrixCommandMetrics creditCardMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(CreditCardCommand.class.getSimpleName())); + HystrixCommandMetrics orderMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetOrderCommand.class.getSimpleName())); + HystrixCommandMetrics userAccountMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetUserAccountCommand.class.getSimpleName())); + HystrixCommandMetrics paymentInformationMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetPaymentInformationCommand.class.getSimpleName())); + + // print out metrics + StringBuilder out = new StringBuilder(); + out.append("\n"); + out.append("#####################################################################################").append("\n"); + out.append("# CreditCardCommand: " + getStatsStringFromMetrics(creditCardMetrics)).append("\n"); + out.append("# GetOrderCommand: " + getStatsStringFromMetrics(orderMetrics)).append("\n"); + out.append("# GetUserAccountCommand: " + getStatsStringFromMetrics(userAccountMetrics)).append("\n"); + out.append("# GetPaymentInformationCommand: " + getStatsStringFromMetrics(paymentInformationMetrics)).append("\n"); + out.append("#####################################################################################").append("\n"); + System.out.println(out.toString()); + } + } + + private String getStatsStringFromMetrics(HystrixCommandMetrics metrics) { + StringBuilder m = new StringBuilder(); + if (metrics != null) { + HealthCounts health = metrics.getHealthCounts(); + m.append("Requests: ").append(health.getTotalRequests()).append(" "); + m.append("Errors: ").append(health.getErrorCount()).append(" (").append(health.getErrorPercentage()).append("%) "); + m.append("Mean: ").append(metrics.getExecutionTimePercentile(50)).append(" "); + m.append("75th: ").append(metrics.getExecutionTimePercentile(75)).append(" "); + m.append("90th: ").append(metrics.getExecutionTimePercentile(90)).append(" "); + m.append("99th: ").append(metrics.getExecutionTimePercentile(99)).append(" "); + } + return m.toString(); + } + + }); + t.setDaemon(true); + t.start(); + } +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/Order.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/Order.java new file mode 100644 index 0000000..77ab037 --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/Order.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012 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.examples.demo; + +import java.net.HttpCookie; + +/** + * POJO + */ +public class Order { + + private final int orderId; + private UserAccount user; + + public Order(int orderId) { + this.orderId = orderId; + + /* a contrived example of calling GetUserAccount again */ + user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).execute(); + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/PaymentInformation.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/PaymentInformation.java new file mode 100644 index 0000000..3bb085d --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/PaymentInformation.java @@ -0,0 +1,47 @@ +/** + * Copyright 2012 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.examples.demo; + +/** + * POJO + */ +public class PaymentInformation { + + private final UserAccount user; + private final String creditCardNumber; + private final int expirationMonth; + private final int expirationYear; + + public PaymentInformation(UserAccount user, String creditCardNumber, int expirationMonth, int expirationYear) { + this.user = user; + this.creditCardNumber = creditCardNumber; + this.expirationMonth = expirationMonth; + this.expirationYear = expirationYear; + } + + public String getCreditCardNumber() { + return creditCardNumber; + } + + public int getExpirationMonth() { + return expirationMonth; + } + + public int getExpirationYear() { + return expirationYear; + } + +} diff --git a/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/UserAccount.java b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/UserAccount.java new file mode 100644 index 0000000..83175ec --- /dev/null +++ b/Hystrix-master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/UserAccount.java @@ -0,0 +1,63 @@ +/** + * Copyright 2012 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.examples.demo; + +/** + * Simple POJO to represent a user and their metadata. + */ +public class UserAccount { + + private final int userId; + private final String name; + private final int accountType; + private final boolean isFeatureXenabled; + private final boolean isFeatureYenabled; + private final boolean isFeatureZenabled; + + public UserAccount(int userId, String name, int accountType, boolean x, boolean y, boolean z) { + this.userId = userId; + this.name = name; + this.accountType = accountType; + this.isFeatureXenabled = x; + this.isFeatureYenabled = y; + this.isFeatureZenabled = z; + } + + public int getUserId() { + return userId; + } + + public String getName() { + return name; + } + + public int getAccountType() { + return accountType; + } + + public boolean isFeatureXenabled() { + return isFeatureXenabled; + } + + public boolean isFeatureYenabled() { + return isFeatureYenabled; + } + + public boolean isFeatureZenabled() { + return isFeatureZenabled; + } + +} diff --git a/Hystrix-master/hystrix-serialization/build.gradle b/Hystrix-master/hystrix-serialization/build.gradle new file mode 100644 index 0000000..f20c021 --- /dev/null +++ b/Hystrix-master/hystrix-serialization/build.gradle @@ -0,0 +1,22 @@ +repositories { + mavenCentral() + jcenter() +} + +sourceCompatibility = JavaVersion.VERSION_1_6 +targetCompatibility = JavaVersion.VERSION_1_6 + +dependencies { + compileApi project(':hystrix-core') + + //if we bump into the the 2.8.0 series, we are forced to use Java7 + compileApi 'com.fasterxml.jackson.core:jackson-core:2.7.5' + compileApi 'com.fasterxml.jackson.core:jackson-databind:2.7.5' + compileApi 'com.fasterxml.jackson.core:jackson-annotations:2.7.5' + compile 'com.fasterxml.jackson.module:jackson-module-afterburner:2.7.5' + + testCompile 'junit:junit-dep:4.10' + testCompile 'org.mockito:mockito-all:1.9.5' + testCompile project(':hystrix-core').sourceSets.test.output + testCompile project(':hystrix-junit') +} diff --git a/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixConfiguration.java b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixConfiguration.java new file mode 100644 index 0000000..e03ab92 --- /dev/null +++ b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixConfiguration.java @@ -0,0 +1,165 @@ +/** + * Copyright 2016 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.serial; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.config.HystrixCollapserConfiguration; +import com.netflix.hystrix.config.HystrixCommandConfiguration; +import com.netflix.hystrix.config.HystrixConfiguration; +import com.netflix.hystrix.config.HystrixThreadPoolConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.util.Map; + +public class SerialHystrixConfiguration extends SerialHystrixMetric { + + private static final Logger logger = LoggerFactory.getLogger(SerialHystrixConfiguration.class); + + @Deprecated + public static byte[] toBytes(HystrixConfiguration config) { + throw new UnsupportedOperationException("Not implemented anymore. Will be implemented in a new class shortly"); + } + + public static String toJsonString(HystrixConfiguration config) { + StringWriter jsonString = new StringWriter(); + + try { + JsonGenerator json = jsonFactory.createGenerator(jsonString); + + serializeConfiguration(config, json); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return jsonString.getBuffer().toString(); + } + + private static void serializeConfiguration(HystrixConfiguration config, JsonGenerator json) { + try { + json.writeStartObject(); + json.writeStringField("type", "HystrixConfig"); + json.writeObjectFieldStart("commands"); + for (Map.Entry entry: config.getCommandConfig().entrySet()) { + final HystrixCommandKey key = entry.getKey(); + final HystrixCommandConfiguration commandConfig = entry.getValue(); + writeCommandConfigJson(json, key, commandConfig); + + } + json.writeEndObject(); + + json.writeObjectFieldStart("threadpools"); + for (Map.Entry entry: config.getThreadPoolConfig().entrySet()) { + final HystrixThreadPoolKey threadPoolKey = entry.getKey(); + final HystrixThreadPoolConfiguration threadPoolConfig = entry.getValue(); + writeThreadPoolConfigJson(json, threadPoolKey, threadPoolConfig); + } + json.writeEndObject(); + + json.writeObjectFieldStart("collapsers"); + for (Map.Entry entry: config.getCollapserConfig().entrySet()) { + final HystrixCollapserKey collapserKey = entry.getKey(); + final HystrixCollapserConfiguration collapserConfig = entry.getValue(); + writeCollapserConfigJson(json, collapserKey, collapserConfig); + } + json.writeEndObject(); + json.writeEndObject(); + json.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + @Deprecated + public static HystrixConfiguration fromByteBuffer(ByteBuffer bb) { + throw new UnsupportedOperationException("Not implemented anymore. Will be implemented in a new class shortly"); + } + + private static void writeCommandConfigJson(JsonGenerator json, HystrixCommandKey key, HystrixCommandConfiguration commandConfig) throws IOException { + json.writeObjectFieldStart(key.name()); + json.writeStringField("threadPoolKey", commandConfig.getThreadPoolKey().name()); + json.writeStringField("groupKey", commandConfig.getGroupKey().name()); + json.writeObjectFieldStart("execution"); + HystrixCommandConfiguration.HystrixCommandExecutionConfig executionConfig = commandConfig.getExecutionConfig(); + json.writeStringField("isolationStrategy", executionConfig.getIsolationStrategy().name()); + json.writeStringField("threadPoolKeyOverride", executionConfig.getThreadPoolKeyOverride()); + json.writeBooleanField("requestCacheEnabled", executionConfig.isRequestCacheEnabled()); + json.writeBooleanField("requestLogEnabled", executionConfig.isRequestLogEnabled()); + json.writeBooleanField("timeoutEnabled", executionConfig.isTimeoutEnabled()); + json.writeBooleanField("fallbackEnabled", executionConfig.isFallbackEnabled()); + json.writeNumberField("timeoutInMilliseconds", executionConfig.getTimeoutInMilliseconds()); + json.writeNumberField("semaphoreSize", executionConfig.getSemaphoreMaxConcurrentRequests()); + json.writeNumberField("fallbackSemaphoreSize", executionConfig.getFallbackMaxConcurrentRequest()); + json.writeBooleanField("threadInterruptOnTimeout", executionConfig.isThreadInterruptOnTimeout()); + json.writeEndObject(); + json.writeObjectFieldStart("metrics"); + HystrixCommandConfiguration.HystrixCommandMetricsConfig metricsConfig = commandConfig.getMetricsConfig(); + json.writeNumberField("healthBucketSizeInMs", metricsConfig.getHealthIntervalInMilliseconds()); + json.writeNumberField("percentileBucketSizeInMilliseconds", metricsConfig.getRollingPercentileBucketSizeInMilliseconds()); + json.writeNumberField("percentileBucketCount", metricsConfig.getRollingCounterNumberOfBuckets()); + json.writeBooleanField("percentileEnabled", metricsConfig.isRollingPercentileEnabled()); + json.writeNumberField("counterBucketSizeInMilliseconds", metricsConfig.getRollingCounterBucketSizeInMilliseconds()); + json.writeNumberField("counterBucketCount", metricsConfig.getRollingCounterNumberOfBuckets()); + json.writeEndObject(); + json.writeObjectFieldStart("circuitBreaker"); + HystrixCommandConfiguration.HystrixCommandCircuitBreakerConfig circuitBreakerConfig = commandConfig.getCircuitBreakerConfig(); + json.writeBooleanField("enabled", circuitBreakerConfig.isEnabled()); + json.writeBooleanField("isForcedOpen", circuitBreakerConfig.isForceOpen()); + json.writeBooleanField("isForcedClosed", circuitBreakerConfig.isForceOpen()); + json.writeNumberField("requestVolumeThreshold", circuitBreakerConfig.getRequestVolumeThreshold()); + json.writeNumberField("errorPercentageThreshold", circuitBreakerConfig.getErrorThresholdPercentage()); + json.writeNumberField("sleepInMilliseconds", circuitBreakerConfig.getSleepWindowInMilliseconds()); + json.writeEndObject(); + json.writeEndObject(); + } + + private static void writeThreadPoolConfigJson(JsonGenerator json, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolConfiguration threadPoolConfig) throws IOException { + json.writeObjectFieldStart(threadPoolKey.name()); + json.writeNumberField("coreSize", threadPoolConfig.getCoreSize()); + json.writeNumberField("maximumSize", threadPoolConfig.getMaximumSize()); + json.writeNumberField("actualMaximumSize", threadPoolConfig.getActualMaximumSize()); + json.writeNumberField("maxQueueSize", threadPoolConfig.getMaxQueueSize()); + json.writeNumberField("queueRejectionThreshold", threadPoolConfig.getQueueRejectionThreshold()); + json.writeNumberField("keepAliveTimeInMinutes", threadPoolConfig.getKeepAliveTimeInMinutes()); + json.writeBooleanField("allowMaximumSizeToDivergeFromCoreSize", threadPoolConfig.getAllowMaximumSizeToDivergeFromCoreSize()); + json.writeNumberField("counterBucketSizeInMilliseconds", threadPoolConfig.getRollingCounterBucketSizeInMilliseconds()); + json.writeNumberField("counterBucketCount", threadPoolConfig.getRollingCounterNumberOfBuckets()); + json.writeEndObject(); + } + + private static void writeCollapserConfigJson(JsonGenerator json, HystrixCollapserKey collapserKey, HystrixCollapserConfiguration collapserConfig) throws IOException { + json.writeObjectFieldStart(collapserKey.name()); + json.writeNumberField("maxRequestsInBatch", collapserConfig.getMaxRequestsInBatch()); + json.writeNumberField("timerDelayInMilliseconds", collapserConfig.getTimerDelayInMilliseconds()); + json.writeBooleanField("requestCacheEnabled", collapserConfig.isRequestCacheEnabled()); + json.writeObjectFieldStart("metrics"); + HystrixCollapserConfiguration.CollapserMetricsConfig metricsConfig = collapserConfig.getCollapserMetricsConfig(); + json.writeNumberField("percentileBucketSizeInMilliseconds", metricsConfig.getRollingPercentileBucketSizeInMilliseconds()); + json.writeNumberField("percentileBucketCount", metricsConfig.getRollingCounterNumberOfBuckets()); + json.writeBooleanField("percentileEnabled", metricsConfig.isRollingPercentileEnabled()); + json.writeNumberField("counterBucketSizeInMilliseconds", metricsConfig.getRollingCounterBucketSizeInMilliseconds()); + json.writeNumberField("counterBucketCount", metricsConfig.getRollingCounterNumberOfBuckets()); + json.writeEndObject(); + json.writeEndObject(); + } +} diff --git a/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixDashboardData.java b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixDashboardData.java new file mode 100644 index 0000000..5754d2f --- /dev/null +++ b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixDashboardData.java @@ -0,0 +1,441 @@ +/** + * Copyright 2016 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.serial; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.netflix.hystrix.HystrixCircuitBreaker; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserMetrics; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.HystrixThreadPoolMetrics; +import com.netflix.hystrix.metric.consumer.HystrixDashboardStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.functions.Func0; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +public class SerialHystrixDashboardData extends SerialHystrixMetric { + + private static final Logger logger = LoggerFactory.getLogger(SerialHystrixDashboardData.class); + + @Deprecated + public static byte[] toBytes(HystrixDashboardStream.DashboardData dashboardData) { + throw new UnsupportedOperationException("Not implemented anymore. Will be implemented in a new class shortly"); + } + + public static String toJsonString(HystrixDashboardStream.DashboardData dashboardData) { + StringWriter jsonString = new StringWriter(); + + try { + JsonGenerator json = jsonFactory.createGenerator(jsonString); + writeDashboardData(json, dashboardData); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return jsonString.getBuffer().toString(); + } + + public static List toMultipleJsonStrings(HystrixDashboardStream.DashboardData dashboardData) { + List jsonStrings = new ArrayList(); + + for (HystrixCommandMetrics commandMetrics : dashboardData.getCommandMetrics()) { + jsonStrings.add(toJsonString(commandMetrics)); + } + + for (HystrixThreadPoolMetrics threadPoolMetrics : dashboardData.getThreadPoolMetrics()) { + jsonStrings.add(toJsonString(threadPoolMetrics)); + } + + for (HystrixCollapserMetrics collapserMetrics : dashboardData.getCollapserMetrics()) { + jsonStrings.add(toJsonString(collapserMetrics)); + } + + return jsonStrings; + } + + private static void writeDashboardData(JsonGenerator json, HystrixDashboardStream.DashboardData dashboardData) { + try { + json.writeStartArray(); + + for (HystrixCommandMetrics commandMetrics : dashboardData.getCommandMetrics()) { + writeCommandMetrics(commandMetrics, json); + } + + for (HystrixThreadPoolMetrics threadPoolMetrics : dashboardData.getThreadPoolMetrics()) { + writeThreadPoolMetrics(threadPoolMetrics, json); + } + + for (HystrixCollapserMetrics collapserMetrics : dashboardData.getCollapserMetrics()) { + writeCollapserMetrics(collapserMetrics, json); + } + + json.writeEndArray(); + + json.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static String toJsonString(HystrixCommandMetrics commandMetrics) { + StringWriter jsonString = new StringWriter(); + + try { + JsonGenerator json = jsonFactory.createGenerator(jsonString); + writeCommandMetrics(commandMetrics, json); + json.close(); + return jsonString.getBuffer().toString(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + public static String toJsonString(HystrixThreadPoolMetrics threadPoolMetrics) { + StringWriter jsonString = new StringWriter(); + + try { + JsonGenerator json = jsonFactory.createGenerator(jsonString); + writeThreadPoolMetrics(threadPoolMetrics, json); + json.close(); + return jsonString.getBuffer().toString(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + public static String toJsonString(HystrixCollapserMetrics collapserMetrics) { + StringWriter jsonString = new StringWriter(); + + try { + JsonGenerator json = jsonFactory.createGenerator(jsonString); + writeCollapserMetrics(collapserMetrics, json); + json.close(); + return jsonString.getBuffer().toString(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + private static void writeCommandMetrics(final HystrixCommandMetrics commandMetrics, JsonGenerator json) throws IOException { + HystrixCommandKey key = commandMetrics.getCommandKey(); + HystrixCircuitBreaker circuitBreaker = HystrixCircuitBreaker.Factory.getInstance(key); + + json.writeStartObject(); + json.writeStringField("type", "HystrixCommand"); + json.writeStringField("name", key.name()); + json.writeStringField("group", commandMetrics.getCommandGroup().name()); + json.writeNumberField("currentTime", System.currentTimeMillis()); + + // circuit breaker + if (circuitBreaker == null) { + // circuit breaker is disabled and thus never open + json.writeBooleanField("isCircuitBreakerOpen", false); + } else { + json.writeBooleanField("isCircuitBreakerOpen", circuitBreaker.isOpen()); + } + HystrixCommandMetrics.HealthCounts healthCounts = commandMetrics.getHealthCounts(); + json.writeNumberField("errorPercentage", healthCounts.getErrorPercentage()); + json.writeNumberField("errorCount", healthCounts.getErrorCount()); + json.writeNumberField("requestCount", healthCounts.getTotalRequests()); + + // rolling counters + safelyWriteNumberField(json, "rollingCountBadRequests", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.BAD_REQUEST); + } + }); + safelyWriteNumberField(json, "rollingCountCollapsedRequests", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.COLLAPSED); + } + }); + safelyWriteNumberField(json, "rollingCountEmit", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.EMIT); + } + }); + safelyWriteNumberField(json, "rollingCountExceptionsThrown", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.EXCEPTION_THROWN); + } + }); + safelyWriteNumberField(json, "rollingCountFailure", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FAILURE); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackEmit", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_EMIT); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackFailure", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_FAILURE); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackMissing", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_MISSING); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackRejection", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_REJECTION); + } + }); + safelyWriteNumberField(json, "rollingCountFallbackSuccess", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.FALLBACK_SUCCESS); + } + }); + safelyWriteNumberField(json, "rollingCountResponsesFromCache", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.RESPONSE_FROM_CACHE); + } + }); + safelyWriteNumberField(json, "rollingCountSemaphoreRejected", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.SEMAPHORE_REJECTED); + } + }); + safelyWriteNumberField(json, "rollingCountShortCircuited", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.SHORT_CIRCUITED); + } + }); + safelyWriteNumberField(json, "rollingCountSuccess", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.SUCCESS); + } + }); + safelyWriteNumberField(json, "rollingCountThreadPoolRejected", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.THREAD_POOL_REJECTED); + } + }); + safelyWriteNumberField(json, "rollingCountTimeout", new Func0() { + @Override + public Long call() { + return commandMetrics.getRollingCount(HystrixEventType.TIMEOUT); + } + }); + + json.writeNumberField("currentConcurrentExecutionCount", commandMetrics.getCurrentConcurrentExecutionCount()); + json.writeNumberField("rollingMaxConcurrentExecutionCount", commandMetrics.getRollingMaxConcurrentExecutions()); + + // latency percentiles + json.writeNumberField("latencyExecute_mean", commandMetrics.getExecutionTimeMean()); + json.writeObjectFieldStart("latencyExecute"); + json.writeNumberField("0", commandMetrics.getExecutionTimePercentile(0)); + json.writeNumberField("25", commandMetrics.getExecutionTimePercentile(25)); + json.writeNumberField("50", commandMetrics.getExecutionTimePercentile(50)); + json.writeNumberField("75", commandMetrics.getExecutionTimePercentile(75)); + json.writeNumberField("90", commandMetrics.getExecutionTimePercentile(90)); + json.writeNumberField("95", commandMetrics.getExecutionTimePercentile(95)); + json.writeNumberField("99", commandMetrics.getExecutionTimePercentile(99)); + json.writeNumberField("99.5", commandMetrics.getExecutionTimePercentile(99.5)); + json.writeNumberField("100", commandMetrics.getExecutionTimePercentile(100)); + json.writeEndObject(); + // + json.writeNumberField("latencyTotal_mean", commandMetrics.getTotalTimeMean()); + json.writeObjectFieldStart("latencyTotal"); + json.writeNumberField("0", commandMetrics.getTotalTimePercentile(0)); + json.writeNumberField("25", commandMetrics.getTotalTimePercentile(25)); + json.writeNumberField("50", commandMetrics.getTotalTimePercentile(50)); + json.writeNumberField("75", commandMetrics.getTotalTimePercentile(75)); + json.writeNumberField("90", commandMetrics.getTotalTimePercentile(90)); + json.writeNumberField("95", commandMetrics.getTotalTimePercentile(95)); + json.writeNumberField("99", commandMetrics.getTotalTimePercentile(99)); + json.writeNumberField("99.5", commandMetrics.getTotalTimePercentile(99.5)); + json.writeNumberField("100", commandMetrics.getTotalTimePercentile(100)); + json.writeEndObject(); + + // property values for reporting what is actually seen by the command rather than what was set somewhere + HystrixCommandProperties commandProperties = commandMetrics.getProperties(); + + json.writeNumberField("propertyValue_circuitBreakerRequestVolumeThreshold", commandProperties.circuitBreakerRequestVolumeThreshold().get()); + json.writeNumberField("propertyValue_circuitBreakerSleepWindowInMilliseconds", commandProperties.circuitBreakerSleepWindowInMilliseconds().get()); + json.writeNumberField("propertyValue_circuitBreakerErrorThresholdPercentage", commandProperties.circuitBreakerErrorThresholdPercentage().get()); + json.writeBooleanField("propertyValue_circuitBreakerForceOpen", commandProperties.circuitBreakerForceOpen().get()); + json.writeBooleanField("propertyValue_circuitBreakerForceClosed", commandProperties.circuitBreakerForceClosed().get()); + json.writeBooleanField("propertyValue_circuitBreakerEnabled", commandProperties.circuitBreakerEnabled().get()); + + json.writeStringField("propertyValue_executionIsolationStrategy", commandProperties.executionIsolationStrategy().get().name()); + json.writeNumberField("propertyValue_executionIsolationThreadTimeoutInMilliseconds", commandProperties.executionTimeoutInMilliseconds().get()); + json.writeNumberField("propertyValue_executionTimeoutInMilliseconds", commandProperties.executionTimeoutInMilliseconds().get()); + json.writeBooleanField("propertyValue_executionIsolationThreadInterruptOnTimeout", commandProperties.executionIsolationThreadInterruptOnTimeout().get()); + json.writeStringField("propertyValue_executionIsolationThreadPoolKeyOverride", commandProperties.executionIsolationThreadPoolKeyOverride().get()); + json.writeNumberField("propertyValue_executionIsolationSemaphoreMaxConcurrentRequests", commandProperties.executionIsolationSemaphoreMaxConcurrentRequests().get()); + json.writeNumberField("propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests", commandProperties.fallbackIsolationSemaphoreMaxConcurrentRequests().get()); + + /* + * The following are commented out as these rarely change and are verbose for streaming for something people don't change. + * We could perhaps allow a property or request argument to include these. + */ + + // json.put("propertyValue_metricsRollingPercentileEnabled", commandProperties.metricsRollingPercentileEnabled().get()); + // json.put("propertyValue_metricsRollingPercentileBucketSize", commandProperties.metricsRollingPercentileBucketSize().get()); + // json.put("propertyValue_metricsRollingPercentileWindow", commandProperties.metricsRollingPercentileWindowInMilliseconds().get()); + // json.put("propertyValue_metricsRollingPercentileWindowBuckets", commandProperties.metricsRollingPercentileWindowBuckets().get()); + // json.put("propertyValue_metricsRollingStatisticalWindowBuckets", commandProperties.metricsRollingStatisticalWindowBuckets().get()); + json.writeNumberField("propertyValue_metricsRollingStatisticalWindowInMilliseconds", commandProperties.metricsRollingStatisticalWindowInMilliseconds().get()); + + json.writeBooleanField("propertyValue_requestCacheEnabled", commandProperties.requestCacheEnabled().get()); + json.writeBooleanField("propertyValue_requestLogEnabled", commandProperties.requestLogEnabled().get()); + + json.writeNumberField("reportingHosts", 1); // this will get summed across all instances in a cluster + json.writeStringField("threadPool", commandMetrics.getThreadPoolKey().name()); + + json.writeEndObject(); + } + + private static void writeThreadPoolMetrics(final HystrixThreadPoolMetrics threadPoolMetrics, JsonGenerator json) throws IOException { + HystrixThreadPoolKey key = threadPoolMetrics.getThreadPoolKey(); + + json.writeStartObject(); + + json.writeStringField("type", "HystrixThreadPool"); + json.writeStringField("name", key.name()); + json.writeNumberField("currentTime", System.currentTimeMillis()); + + json.writeNumberField("currentActiveCount", threadPoolMetrics.getCurrentActiveCount().intValue()); + json.writeNumberField("currentCompletedTaskCount", threadPoolMetrics.getCurrentCompletedTaskCount().longValue()); + json.writeNumberField("currentCorePoolSize", threadPoolMetrics.getCurrentCorePoolSize().intValue()); + json.writeNumberField("currentLargestPoolSize", threadPoolMetrics.getCurrentLargestPoolSize().intValue()); + json.writeNumberField("currentMaximumPoolSize", threadPoolMetrics.getCurrentMaximumPoolSize().intValue()); + json.writeNumberField("currentPoolSize", threadPoolMetrics.getCurrentPoolSize().intValue()); + json.writeNumberField("currentQueueSize", threadPoolMetrics.getCurrentQueueSize().intValue()); + json.writeNumberField("currentTaskCount", threadPoolMetrics.getCurrentTaskCount().longValue()); + safelyWriteNumberField(json, "rollingCountThreadsExecuted", new Func0() { + @Override + public Long call() { + return threadPoolMetrics.getRollingCount(HystrixEventType.ThreadPool.EXECUTED); + } + }); + json.writeNumberField("rollingMaxActiveThreads", threadPoolMetrics.getRollingMaxActiveThreads()); + safelyWriteNumberField(json, "rollingCountCommandRejections", new Func0() { + @Override + public Long call() { + return threadPoolMetrics.getRollingCount(HystrixEventType.ThreadPool.REJECTED); + } + }); + + json.writeNumberField("propertyValue_queueSizeRejectionThreshold", threadPoolMetrics.getProperties().queueSizeRejectionThreshold().get()); + json.writeNumberField("propertyValue_metricsRollingStatisticalWindowInMilliseconds", threadPoolMetrics.getProperties().metricsRollingStatisticalWindowInMilliseconds().get()); + + json.writeNumberField("reportingHosts", 1); // this will get summed across all instances in a cluster + + json.writeEndObject(); + } + + private static void writeCollapserMetrics(final HystrixCollapserMetrics collapserMetrics, JsonGenerator json) throws IOException { + HystrixCollapserKey key = collapserMetrics.getCollapserKey(); + + json.writeStartObject(); + + json.writeStringField("type", "HystrixCollapser"); + json.writeStringField("name", key.name()); + json.writeNumberField("currentTime", System.currentTimeMillis()); + + safelyWriteNumberField(json, "rollingCountRequestsBatched", new Func0() { + @Override + public Long call() { + return collapserMetrics.getRollingCount(HystrixEventType.Collapser.ADDED_TO_BATCH); + } + }); + safelyWriteNumberField(json, "rollingCountBatches", new Func0() { + @Override + public Long call() { + return collapserMetrics.getRollingCount(HystrixEventType.Collapser.BATCH_EXECUTED); + } + }); + safelyWriteNumberField(json, "rollingCountResponsesFromCache", new Func0() { + @Override + public Long call() { + return collapserMetrics.getRollingCount(HystrixEventType.Collapser.RESPONSE_FROM_CACHE); + } + }); + + // batch size percentiles + json.writeNumberField("batchSize_mean", collapserMetrics.getBatchSizeMean()); + json.writeObjectFieldStart("batchSize"); + json.writeNumberField("25", collapserMetrics.getBatchSizePercentile(25)); + json.writeNumberField("50", collapserMetrics.getBatchSizePercentile(50)); + json.writeNumberField("75", collapserMetrics.getBatchSizePercentile(75)); + json.writeNumberField("90", collapserMetrics.getBatchSizePercentile(90)); + json.writeNumberField("95", collapserMetrics.getBatchSizePercentile(95)); + json.writeNumberField("99", collapserMetrics.getBatchSizePercentile(99)); + json.writeNumberField("99.5", collapserMetrics.getBatchSizePercentile(99.5)); + json.writeNumberField("100", collapserMetrics.getBatchSizePercentile(100)); + json.writeEndObject(); + + // shard size percentiles (commented-out for now) + //json.writeNumberField("shardSize_mean", collapserMetrics.getShardSizeMean()); + //json.writeObjectFieldStart("shardSize"); + //json.writeNumberField("25", collapserMetrics.getShardSizePercentile(25)); + //json.writeNumberField("50", collapserMetrics.getShardSizePercentile(50)); + //json.writeNumberField("75", collapserMetrics.getShardSizePercentile(75)); + //json.writeNumberField("90", collapserMetrics.getShardSizePercentile(90)); + //json.writeNumberField("95", collapserMetrics.getShardSizePercentile(95)); + //json.writeNumberField("99", collapserMetrics.getShardSizePercentile(99)); + //json.writeNumberField("99.5", collapserMetrics.getShardSizePercentile(99.5)); + //json.writeNumberField("100", collapserMetrics.getShardSizePercentile(100)); + //json.writeEndObject(); + + //json.writeNumberField("propertyValue_metricsRollingStatisticalWindowInMilliseconds", collapserMetrics.getProperties().metricsRollingStatisticalWindowInMilliseconds().get()); + json.writeBooleanField("propertyValue_requestCacheEnabled", collapserMetrics.getProperties().requestCacheEnabled().get()); + json.writeNumberField("propertyValue_maxRequestsInBatch", collapserMetrics.getProperties().maxRequestsInBatch().get()); + json.writeNumberField("propertyValue_timerDelayInMilliseconds", collapserMetrics.getProperties().timerDelayInMilliseconds().get()); + + json.writeNumberField("reportingHosts", 1); // this will get summed across all instances in a cluster + + json.writeEndObject(); + } + + protected static void safelyWriteNumberField(JsonGenerator json, String name, Func0 metricGenerator) throws IOException { + try { + json.writeNumberField(name, metricGenerator.call()); + } catch (NoSuchFieldError error) { + logger.error("While publishing Hystrix metrics stream, error looking up eventType for : " + name + ". Please check that all Hystrix versions are the same!"); + json.writeNumberField(name, 0L); + } + } +} diff --git a/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixMetric.java b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixMetric.java new file mode 100644 index 0000000..905a1e8 --- /dev/null +++ b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixMetric.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 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.serial; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; + +public class SerialHystrixMetric { + protected final static JsonFactory jsonFactory = new JsonFactory(); + protected final static ObjectMapper mapper = new ObjectMapper(); + protected final static Logger logger = LoggerFactory.getLogger(SerialHystrixMetric.class); + + @Deprecated + public static String fromByteBufferToString(ByteBuffer bb) { + throw new UnsupportedOperationException("Not implemented anymore. Will be implemented in a new class shortly"); + } +} diff --git a/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixRequestEvents.java b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixRequestEvents.java new file mode 100644 index 0000000..8b1e1d6 --- /dev/null +++ b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixRequestEvents.java @@ -0,0 +1,101 @@ +/** + * Copyright 2016 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.serial; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.netflix.hystrix.ExecutionResult; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.metric.HystrixRequestEvents; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; + +public class SerialHystrixRequestEvents extends SerialHystrixMetric { + + @Deprecated + public static byte[] toBytes(HystrixRequestEvents requestEvents) { + throw new UnsupportedOperationException("Not implemented anymore. Will be implemented in a new class shortly"); + } + + public static String toJsonString(HystrixRequestEvents requestEvents) { + StringWriter jsonString = new StringWriter(); + + try { + JsonGenerator json = jsonFactory.createGenerator(jsonString); + + serializeRequestEvents(requestEvents, json); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return jsonString.getBuffer().toString(); + } + + private static void serializeRequestEvents(HystrixRequestEvents requestEvents, JsonGenerator json) { + try { + json.writeStartArray(); + + for (Map.Entry> entry: requestEvents.getExecutionsMappedToLatencies().entrySet()) { + convertExecutionToJson(json, entry.getKey(), entry.getValue()); + } + + json.writeEndArray(); + json.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void convertExecutionToJson(JsonGenerator json, HystrixRequestEvents.ExecutionSignature executionSignature, List latencies) throws IOException { + json.writeStartObject(); + json.writeStringField("name", executionSignature.getCommandName()); + json.writeArrayFieldStart("events"); + ExecutionResult.EventCounts eventCounts = executionSignature.getEventCounts(); + for (HystrixEventType eventType: HystrixEventType.values()) { + if (!eventType.equals(HystrixEventType.COLLAPSED)) { + if (eventCounts.contains(eventType)) { + int eventCount = eventCounts.getCount(eventType); + if (eventCount > 1) { + json.writeStartObject(); + json.writeStringField("name", eventType.name()); + json.writeNumberField("count", eventCount); + json.writeEndObject(); + } else { + json.writeString(eventType.name()); + } + } + } + } + json.writeEndArray(); + json.writeArrayFieldStart("latencies"); + for (int latency: latencies) { + json.writeNumber(latency); + } + json.writeEndArray(); + if (executionSignature.getCachedCount() > 0) { + json.writeNumberField("cached", executionSignature.getCachedCount()); + } + if (executionSignature.getEventCounts().contains(HystrixEventType.COLLAPSED)) { + json.writeObjectFieldStart("collapsed"); + json.writeStringField("name", executionSignature.getCollapserKey().name()); + json.writeNumberField("count", executionSignature.getCollapserBatchSize()); + json.writeEndObject(); + } + json.writeEndObject(); + } +} diff --git a/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixUtilization.java b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixUtilization.java new file mode 100644 index 0000000..56465f1 --- /dev/null +++ b/Hystrix-master/hystrix-serialization/src/main/java/com/netflix/hystrix/serial/SerialHystrixUtilization.java @@ -0,0 +1,101 @@ +/** + * Copyright 2016 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.serial; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.metric.sample.HystrixCommandUtilization; +import com.netflix.hystrix.metric.sample.HystrixThreadPoolUtilization; +import com.netflix.hystrix.metric.sample.HystrixUtilization; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.util.Map; + +public class SerialHystrixUtilization extends SerialHystrixMetric { + + private final static Logger logger = LoggerFactory.getLogger(SerialHystrixUtilization.class); + + @Deprecated + public static byte[] toBytes(HystrixUtilization utilization) { + throw new UnsupportedOperationException("Not implemented anymore. Will be implemented in a new class shortly"); + } + + public static String toJsonString(HystrixUtilization utilization) { + StringWriter jsonString = new StringWriter(); + + try { + JsonGenerator json = jsonFactory.createGenerator(jsonString); + + serializeUtilization(utilization, json); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return jsonString.getBuffer().toString(); + } + + private static void serializeUtilization(HystrixUtilization utilization, JsonGenerator json) { + try { + json.writeStartObject(); + json.writeStringField("type", "HystrixUtilization"); + json.writeObjectFieldStart("commands"); + for (Map.Entry entry: utilization.getCommandUtilizationMap().entrySet()) { + final HystrixCommandKey key = entry.getKey(); + final HystrixCommandUtilization commandUtilization = entry.getValue(); + writeCommandUtilizationJson(json, key, commandUtilization); + + } + json.writeEndObject(); + + json.writeObjectFieldStart("threadpools"); + for (Map.Entry entry: utilization.getThreadPoolUtilizationMap().entrySet()) { + final HystrixThreadPoolKey threadPoolKey = entry.getKey(); + final HystrixThreadPoolUtilization threadPoolUtilization = entry.getValue(); + writeThreadPoolUtilizationJson(json, threadPoolKey, threadPoolUtilization); + } + json.writeEndObject(); + json.writeEndObject(); + json.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Deprecated + public static HystrixUtilization fromByteBuffer(ByteBuffer bb) { + throw new UnsupportedOperationException("Not implemented anymore. Will be implemented in a new class shortly"); + } + + private static void writeCommandUtilizationJson(JsonGenerator json, HystrixCommandKey key, HystrixCommandUtilization utilization) throws IOException { + json.writeObjectFieldStart(key.name()); + json.writeNumberField("activeCount", utilization.getConcurrentCommandCount()); + json.writeEndObject(); + } + + private static void writeThreadPoolUtilizationJson(JsonGenerator json, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolUtilization utilization) throws IOException { + json.writeObjectFieldStart(threadPoolKey.name()); + json.writeNumberField("activeCount", utilization.getCurrentActiveCount()); + json.writeNumberField("queueSize", utilization.getCurrentQueueSize()); + json.writeNumberField("corePoolSize", utilization.getCurrentCorePoolSize()); + json.writeNumberField("poolSize", utilization.getCurrentPoolSize()); + json.writeEndObject(); + } +} diff --git a/Hystrix-master/hystrix-serialization/src/test/java/com/netflix/hystrix/serial/SerialHystrixRequestEventsTest.java b/Hystrix-master/hystrix-serialization/src/test/java/com/netflix/hystrix/serial/SerialHystrixRequestEventsTest.java new file mode 100644 index 0000000..86231e3 --- /dev/null +++ b/Hystrix-master/hystrix-serialization/src/test/java/com/netflix/hystrix/serial/SerialHystrixRequestEventsTest.java @@ -0,0 +1,456 @@ +/** + * Copyright 2016 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.serial; + +import com.netflix.hystrix.ExecutionResult; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.metric.HystrixRequestEvents; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SerialHystrixRequestEventsTest { + + private static final HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("GROUP"); + private static final HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("ThreadPool"); + private static final HystrixCommandKey fooKey = HystrixCommandKey.Factory.asKey("Foo"); + private static final HystrixCommandKey barKey = HystrixCommandKey.Factory.asKey("Bar"); + private static final HystrixCollapserKey collapserKey = HystrixCollapserKey.Factory.asKey("FooCollapser"); + + @Test + public void testEmpty() throws IOException { + HystrixRequestEvents request = new HystrixRequestEvents(new ArrayList>()); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[]", actual); + } + + @Test + public void testSingleSuccess() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 100, HystrixEventType.SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[100]}]", actual); + } + + @Test + public void testSingleFailureFallbackMissing() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 101, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"FAILURE\",\"FALLBACK_MISSING\"],\"latencies\":[101]}]", actual); + } + + @Test + public void testSingleFailureFallbackSuccess() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 102, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"FAILURE\",\"FALLBACK_SUCCESS\"],\"latencies\":[102]}]", actual); + } + + @Test + public void testSingleFailureFallbackRejected() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 103, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_REJECTION)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"FAILURE\",\"FALLBACK_REJECTION\"],\"latencies\":[103]}]", actual); + } + + @Test + public void testSingleFailureFallbackFailure() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 104, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_FAILURE)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"FAILURE\",\"FALLBACK_FAILURE\"],\"latencies\":[104]}]", actual); + } + + @Test + public void testSingleTimeoutFallbackSuccess() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 105, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"TIMEOUT\",\"FALLBACK_SUCCESS\"],\"latencies\":[105]}]", actual); + } + + @Test + public void testSingleSemaphoreRejectedFallbackSuccess() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 1, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"SEMAPHORE_REJECTED\",\"FALLBACK_SUCCESS\"],\"latencies\":[1]}]", actual); + } + + @Test + public void testSingleThreadPoolRejectedFallbackSuccess() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 1, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"THREAD_POOL_REJECTED\",\"FALLBACK_SUCCESS\"],\"latencies\":[1]}]", actual); + } + + @Test + public void testSingleShortCircuitedFallbackSuccess() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 1, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"SHORT_CIRCUITED\",\"FALLBACK_SUCCESS\"],\"latencies\":[1]}]", actual); + } + + @Test + public void testSingleBadRequest() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 50, HystrixEventType.BAD_REQUEST)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"BAD_REQUEST\"],\"latencies\":[50]}]", actual); + } + + @Test + public void testTwoSuccessesSameKey() throws IOException { + List> executions = new ArrayList>(); + HystrixInvokableInfo foo1 = new SimpleExecution(fooKey, 23, HystrixEventType.SUCCESS); + HystrixInvokableInfo foo2 = new SimpleExecution(fooKey, 34, HystrixEventType.SUCCESS); + executions.add(foo1); + executions.add(foo2); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[23,34]}]", actual); + } + + @Test + public void testTwoSuccessesDifferentKey() throws IOException { + List> executions = new ArrayList>(); + HystrixInvokableInfo foo1 = new SimpleExecution(fooKey, 23, HystrixEventType.SUCCESS); + HystrixInvokableInfo bar1 = new SimpleExecution(barKey, 34, HystrixEventType.SUCCESS); + executions.add(foo1); + executions.add(bar1); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertTrue(actual.equals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[23]},{\"name\":\"Bar\",\"events\":[\"SUCCESS\"],\"latencies\":[34]}]") || + actual.equals("[{\"name\":\"Bar\",\"events\":[\"SUCCESS\"],\"latencies\":[34]},{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[23]}]")); + } + + @Test + public void testTwoFailuresSameKey() throws IOException { + List> executions = new ArrayList>(); + HystrixInvokableInfo foo1 = new SimpleExecution(fooKey, 56, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + HystrixInvokableInfo foo2 = new SimpleExecution(fooKey, 67, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + executions.add(foo1); + executions.add(foo2); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"FAILURE\",\"FALLBACK_SUCCESS\"],\"latencies\":[56,67]}]", actual); + } + + @Test + public void testTwoSuccessesOneFailureSameKey() throws IOException { + List> executions = new ArrayList>(); + HystrixInvokableInfo foo1 = new SimpleExecution(fooKey, 10, HystrixEventType.SUCCESS); + HystrixInvokableInfo foo2 = new SimpleExecution(fooKey, 67, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS); + HystrixInvokableInfo foo3 = new SimpleExecution(fooKey, 11, HystrixEventType.SUCCESS); + executions.add(foo1); + executions.add(foo2); + executions.add(foo3); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertTrue(actual.equals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[10,11]},{\"name\":\"Foo\",\"events\":[\"FAILURE\",\"FALLBACK_SUCCESS\"],\"latencies\":[67]}]") || + actual.equals("[{\"name\":\"Foo\",\"events\":[\"FAILURE\",\"FALLBACK_SUCCESS\"],\"latencies\":[67]},{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[10,11]}]")); + } + + @Test + public void testSingleResponseFromCache() throws IOException { + List> executions = new ArrayList>(); + HystrixInvokableInfo foo1 = new SimpleExecution(fooKey, 23, "cacheKeyA", HystrixEventType.SUCCESS); + HystrixInvokableInfo cachedFoo1 = new SimpleExecution(fooKey, "cacheKeyA"); + executions.add(foo1); + executions.add(cachedFoo1); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[23],\"cached\":1}]", actual); + } + + @Test + public void testMultipleResponsesFromCache() throws IOException { + List> executions = new ArrayList>(); + HystrixInvokableInfo foo1 = new SimpleExecution(fooKey, 23, "cacheKeyA", HystrixEventType.SUCCESS); + HystrixInvokableInfo cachedFoo1 = new SimpleExecution(fooKey, "cacheKeyA"); + HystrixInvokableInfo anotherCachedFoo1 = new SimpleExecution(fooKey, "cacheKeyA"); + executions.add(foo1); + executions.add(cachedFoo1); + executions.add(anotherCachedFoo1); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[23],\"cached\":2}]", actual); + } + + @Test + public void testMultipleCacheKeys() throws IOException { + List> executions = new ArrayList>(); + HystrixInvokableInfo foo1 = new SimpleExecution(fooKey, 23, "cacheKeyA", HystrixEventType.SUCCESS); + HystrixInvokableInfo cachedFoo1 = new SimpleExecution(fooKey, "cacheKeyA"); + HystrixInvokableInfo foo2 = new SimpleExecution(fooKey, 67, "cacheKeyB", HystrixEventType.SUCCESS); + HystrixInvokableInfo cachedFoo2 = new SimpleExecution(fooKey, "cacheKeyB"); + executions.add(foo1); + executions.add(cachedFoo1); + executions.add(foo2); + executions.add(cachedFoo2); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertTrue(actual.equals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[67],\"cached\":1},{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[23],\"cached\":1}]") || + actual.equals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[23],\"cached\":1},{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[67],\"cached\":1}]")); + } + + @Test + public void testSingleSuccessMultipleEmits() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 100, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[{\"name\":\"EMIT\",\"count\":3},\"SUCCESS\"],\"latencies\":[100]}]", actual); + } + + @Test + public void testSingleSuccessMultipleEmitsAndFallbackEmits() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 100, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[{\"name\":\"EMIT\",\"count\":3},\"FAILURE\",{\"name\":\"FALLBACK_EMIT\",\"count\":2},\"FALLBACK_SUCCESS\"],\"latencies\":[100]}]", actual); + } + + @Test + public void testCollapsedBatchOfOne() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 53, collapserKey, 1, HystrixEventType.SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[53],\"collapsed\":{\"name\":\"FooCollapser\",\"count\":1}}]", actual); + } + + @Test + public void testCollapsedBatchOfSix() throws IOException { + List> executions = new ArrayList>(); + executions.add(new SimpleExecution(fooKey, 53, collapserKey, 6, HystrixEventType.SUCCESS)); + HystrixRequestEvents request = new HystrixRequestEvents(executions); + String actual = SerialHystrixRequestEvents.toJsonString(request); + assertEquals("[{\"name\":\"Foo\",\"events\":[\"SUCCESS\"],\"latencies\":[53],\"collapsed\":{\"name\":\"FooCollapser\",\"count\":6}}]", actual); + } + + private class SimpleExecution implements HystrixInvokableInfo { + private final HystrixCommandKey commandKey; + private final ExecutionResult executionResult; + private final String cacheKey; + private final HystrixCollapserKey collapserKey; + + public SimpleExecution(HystrixCommandKey commandKey, int latency, HystrixEventType... events) { + this.commandKey = commandKey; + this.executionResult = ExecutionResult.from(events).setExecutionLatency(latency); + this.cacheKey = null; + this.collapserKey = null; + } + + public SimpleExecution(HystrixCommandKey commandKey, int latency, String cacheKey, HystrixEventType... events) { + this.commandKey = commandKey; + this.executionResult = ExecutionResult.from(events).setExecutionLatency(latency); + this.cacheKey = cacheKey; + this.collapserKey = null; + } + + public SimpleExecution(HystrixCommandKey commandKey, String cacheKey) { + this.commandKey = commandKey; + this.executionResult = ExecutionResult.from(HystrixEventType.RESPONSE_FROM_CACHE); + this.cacheKey = cacheKey; + this.collapserKey = null; + } + + public SimpleExecution(HystrixCommandKey commandKey, int latency, HystrixCollapserKey collapserKey, int batchSize, HystrixEventType... events) { + this.commandKey = commandKey; + ExecutionResult interimResult = ExecutionResult.from(events).setExecutionLatency(latency); + for (int i = 0; i < batchSize; i++) { + interimResult = interimResult.addEvent(HystrixEventType.COLLAPSED); + } + this.executionResult = interimResult; + this.cacheKey = null; + this.collapserKey = collapserKey; + } + + @Override + public HystrixCommandGroupKey getCommandGroup() { + return groupKey; + } + + @Override + public HystrixCommandKey getCommandKey() { + return commandKey; + } + + @Override + public HystrixThreadPoolKey getThreadPoolKey() { + return threadPoolKey; + } + + @Override + public String getPublicCacheKey() { + return cacheKey; + } + + @Override + public HystrixCollapserKey getOriginatingCollapserKey() { + return collapserKey; + } + + @Override + public HystrixCommandMetrics getMetrics() { + return null; + } + + @Override + public HystrixCommandProperties getProperties() { + return null; + } + + @Override + public boolean isCircuitBreakerOpen() { + return false; + } + + @Override + public boolean isExecutionComplete() { + return true; + } + + @Override + public boolean isExecutedInThread() { + return false; //do i want this? + } + + @Override + public boolean isSuccessfulExecution() { + return executionResult.getEventCounts().contains(HystrixEventType.SUCCESS); + } + + @Override + public boolean isFailedExecution() { + return executionResult.getEventCounts().contains(HystrixEventType.FAILURE); + } + + @Override + public Throwable getFailedExecutionException() { + return null; + } + + @Override + public boolean isResponseFromFallback() { + return executionResult.getEventCounts().contains(HystrixEventType.FALLBACK_SUCCESS); + } + + @Override + public boolean isResponseTimedOut() { + return executionResult.getEventCounts().contains(HystrixEventType.TIMEOUT); + } + + @Override + public boolean isResponseShortCircuited() { + return executionResult.getEventCounts().contains(HystrixEventType.SHORT_CIRCUITED); + } + + @Override + public boolean isResponseFromCache() { + return executionResult.getEventCounts().contains(HystrixEventType.RESPONSE_FROM_CACHE); + } + + @Override + public boolean isResponseRejected() { + return executionResult.isResponseRejected(); + } + + @Override + public boolean isResponseSemaphoreRejected() { + return executionResult.getEventCounts().contains(HystrixEventType.SEMAPHORE_REJECTED); + } + + @Override + public boolean isResponseThreadPoolRejected() { + return executionResult.getEventCounts().contains(HystrixEventType.THREAD_POOL_REJECTED); + } + + @Override + public List getExecutionEvents() { + return executionResult.getOrderedList(); + } + + @Override + public int getNumberEmissions() { + return executionResult.getEventCounts().getCount(HystrixEventType.EMIT); + } + + @Override + public int getNumberFallbackEmissions() { + return executionResult.getEventCounts().getCount(HystrixEventType.FALLBACK_EMIT); + } + + @Override + public int getNumberCollapsed() { + return executionResult.getEventCounts().getCount(HystrixEventType.COLLAPSED); + } + + @Override + public int getExecutionTimeInMilliseconds() { + return executionResult.getExecutionLatency(); + } + + @Override + public long getCommandRunStartTimeInNanos() { + return System.currentTimeMillis(); + } + + @Override + public ExecutionResult.EventCounts getEventCounts() { + return executionResult.getEventCounts(); + } + + @Override + public String toString() { + return "SimpleExecution{" + + "commandKey=" + commandKey.name() + + ", executionResult=" + executionResult + + ", cacheKey='" + cacheKey + '\'' + + ", collapserKey=" + collapserKey + + '}'; + } + } +} diff --git a/Hystrix-master/settings.gradle b/Hystrix-master/settings.gradle new file mode 100644 index 0000000..38c4b1a --- /dev/null +++ b/Hystrix-master/settings.gradle @@ -0,0 +1,28 @@ +rootProject.name='hystrix' +include 'hystrix-core', \ +'hystrix-examples', \ +'hystrix-examples-webapp', \ +'hystrix-contrib/hystrix-clj', \ +'hystrix-contrib/hystrix-request-servlet', \ +'hystrix-contrib/hystrix-servo-metrics-publisher', \ +'hystrix-contrib/hystrix-metrics-event-stream', \ +'hystrix-contrib/hystrix-metrics-event-stream-jaxrs', \ +'hystrix-contrib/hystrix-rx-netty-metrics-stream', \ +'hystrix-contrib/hystrix-codahale-metrics-publisher', \ +'hystrix-contrib/hystrix-yammer-metrics-publisher', \ +'hystrix-contrib/hystrix-network-auditor-agent', \ +'hystrix-contrib/hystrix-javanica', \ +'hystrix-contrib/hystrix-junit', \ +'hystrix-serialization' + +project(':hystrix-contrib/hystrix-clj').name = 'hystrix-clj' +project(':hystrix-contrib/hystrix-request-servlet').name = 'hystrix-request-servlet' +project(':hystrix-contrib/hystrix-servo-metrics-publisher').name = 'hystrix-servo-metrics-publisher' +project(':hystrix-contrib/hystrix-metrics-event-stream').name = 'hystrix-metrics-event-stream' +project(':hystrix-contrib/hystrix-metrics-event-stream-jaxrs').name = 'hystrix-metrics-event-stream-jaxrs' +project(':hystrix-contrib/hystrix-rx-netty-metrics-stream').name = 'hystrix-rx-netty-metrics-stream' +project(':hystrix-contrib/hystrix-codahale-metrics-publisher').name = 'hystrix-codahale-metrics-publisher' +project(':hystrix-contrib/hystrix-yammer-metrics-publisher').name = 'hystrix-yammer-metrics-publisher' +project(':hystrix-contrib/hystrix-network-auditor-agent').name = 'hystrix-network-auditor-agent' +project(':hystrix-contrib/hystrix-javanica').name = 'hystrix-javanica' +project(':hystrix-contrib/hystrix-junit').name = 'hystrix-junit'