Resilience4clj is a lightweight fault tolerance library set built on top of GitHub's Resilience4j and inspired by Netflix Hystrix. It was designed for Clojure and functional programming with composability in mind.
Read more about the motivation and details of Resilience4clj here.
Resilience4Clj Time Limiter lets you decorate a function call with a specified time limit. If the function does not return within the established time, an exception is thrown. This is particularly useful for external dependencies that may be too slow and you have time budgets for them.
Add resilience4clj/resilience4clj-timelimiter as a dependency to
your deps.edn file:
resilience4clj/resilience4clj-timelimiter {:mvn/version "0.1.1"}If you are using lein instead, add it as a dependency to your
project.clj file:
[resilience4clj/resilience4clj-timelimiter "0.1.1"]Require the library:
(require '[resilience4clj-timelimiter.core :as tl])Then create a time limiter calling the function create:
(def limiter (tl/create))Now you can decorate any function you have with the limiter you just defined.
For the sake of this example, let's create a function that takes 250ms to return:
(defn slow-hello []
(Thread/sleep 250)
"Hello World!")The function decorate will take the slow function just above and the
time limiter you created and return a time limited function:
(def limited-hello (tl/decorate slow-hello limiter))When you call limited-hello, it should eval to "Hello World!"
after 250ms:
(limited-hello) ;; => "Hello World!" (after 250ms)The default configuration of the limiter is to wait 1000ms (1s) that's
why nothing really exceptional happened. If you were to redefined
slow-hello to take 1500ms though:
(defn slow-hello []
(Thread/sleep 1500)
"Hello World!")Then calling limited-hello (after also redefining it) would yield an
exception:
(limited-hello) ;; => throws ExceptionInfoWhen creating a time limiter, you can fine tune two of its settings:
:timeout-duration- the timeout for this limiter in milliseconds (i.e. 300). Default is 1000.:cancel-running-future?- whentruethe call to the decorated function will be canceled in case of a timeout. Iffalse, the call will go through even in case of a timeout. However, in that case, the caller will still get an exception thrown. Default istrue.
Changing :cancel-running-future? to false is particularly useful
if you have an external call with a side effect that might be relevant
later (i.e. updating a cache with external data - in that case you
might prefer to fail the first time around if your time budget expires
but still get the cache eventually updated when the external system
returns).
These two options can be sent to create as a map. In the following
example, any function decorated with limiter will timeout in 300ms
and calls will not be canceled if a timeout occurs.
(def limiter (tl/create {:timeout-duration 300
:cancel-running-future? false}))The function config returns the configuration of a limiter in case
you need to inspect it. Example:
(tl/config limiter)
=> {:timeout-duration 1000
:cancel-running-future? true}When a timeout occurs, an instance of ExceptionInfo will be
thrown. Its payload can be extracted with ex-data and the key
:resilience4clj.anomaly/category will be set with
:resilience4clj.anomaly/execution-timeout.
(try
(slow-hello)
(catch Throwable e
(if (= :resilience4clj.anomaly/execution-timeout
(-> e ex-data :resilience4clj.anomaly/category))
(println "Call timed out!!!"))))Resilience4clj is composed of several modules that easily compose together. For instance, if you are also using the circuit breaker module and assuming your import and basic settings look like this:
(ns my-app
(:require [resilience4clj-circuitbreaker.core :as cb]
[resilience4clj-timelimiter.core :as tl]))
;; create time limiter with default settings
(def limiter (tl/create))
;; create circuit breaker with default settings
(def breaker (cb/create "HelloService"))
;; slow function you want to limit
(defn slow-hello []
(Thread/sleep 1500)
"Hello World!")Then you can create a protected call that combines both the time limiter and the circuit breaker:
(def protected-hello (-> slow-hello
(tl/decorate limiter)
(cb/decorate breaker)))The resulting function on protected-hello will trigger the breaker
in case of a timeout now.
If you find a bug, submit a Github issue.
This project is looking for team members who can help this project succeed! If you are interested in becoming a team member please open an issue.
Copyright © 2019 Tiago Luchini
Distributed under the MIT License.