From d4f992a3597172fa4dcf5e6b83feb5c51a79f360 Mon Sep 17 00:00:00 2001 From: Dave Ray Date: Thu, 25 Jul 2013 21:31:07 -0700 Subject: [PATCH] Expose current HystrixCommand to fns @ohpauleez noticed that the current HystrixCommand was inaccessible from :fallback-fn. This change introduces a dynamic var, *command* that exposes the currently running HystrixCommand instance so it can be interrogated as needed. At some point, if the dynamic var doesn't work out or proves error-prone or unwieldy, we may introduce alternative functions that explicitly pass the function as the first arg. --- .../main/clojure/com/netflix/hystrix/core.clj | 27 ++++++++++++++++--- .../clojure/com/netflix/hystrix/core_test.clj | 25 +++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/hystrix-contrib/hystrix-clj/src/main/clojure/com/netflix/hystrix/core.clj b/hystrix-contrib/hystrix-clj/src/main/clojure/com/netflix/hystrix/core.clj index 2484b3431..d88987035 100644 --- a/hystrix-contrib/hystrix-clj/src/main/clojure/com/netflix/hystrix/core.clj +++ b/hystrix-contrib/hystrix-clj/src/main/clojure/com/netflix/hystrix/core.clj @@ -277,6 +277,21 @@ ;################################################################################ +(def ^:dynamic *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. @@ -616,10 +631,14 @@ (when (not (instance? HystrixCommand$Setter setter)) (throw (IllegalStateException. (str ":init-fn didn't return HystrixCommand$Setter instance")))) (proxy [HystrixCommand] [^HystrixCommand$Setter setter] - (run [] (apply run-fn args)) - (getFallback [] (if fallback-fn - (apply fallback-fn args) - (throw (UnsupportedOperationException. "No :fallback-fn provided")))) + (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)))))) diff --git a/hystrix-contrib/hystrix-clj/src/test/clojure/com/netflix/hystrix/core_test.clj b/hystrix-contrib/hystrix-clj/src/test/clojure/com/netflix/hystrix/core_test.clj index 1da742058..d23408a8b 100644 --- a/hystrix-contrib/hystrix-clj/src/test/clojure/com/netflix/hystrix/core_test.clj +++ b/hystrix-contrib/hystrix-clj/src/test/clojure/com/netflix/hystrix/core_test.clj @@ -172,6 +172,31 @@ (is (= "hello-world" (.get qc) @qc)) (is (.isDone qc)))))) +(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.