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 ExceptionInfo
When 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?
- whentrue
the 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.