Back
chime-at (clj)
(source)function
(chime-at times f)
(chime-at times f {:keys [error-handler on-finished thread-factory clock], :or {error-handler default-error-handler, thread-factory default-thread-factory, clock *clock*}})
Calls `f` with the current time at every time in the `times` sequence.
```
(:require [chime.core :as chime])
(:import [java.time Instant])
(let [now (Instant/now)]
(chime/chime-at [(.plusSeconds now 2)
(.plusSeconds now 4)]
(fn [time]
(println "Chiming at" time)))
```
Returns an AutoCloseable that you can `.close` to stop the schedule.
You can also deref the return value to wait for the schedule to finish.
Providing a custom `thread-factory` is supported, but optional (see `chime.core/default-thread-factory`).
Providing a custom `clock` is supported, but optional (see `chime.core/utc-clock`).
When the schedule is either cancelled or finished, will call the `on-finished` handler.
You can pass an error-handler to `chime-at` - a function that takes the exception as an argument.
Return truthy from this function to continue the schedule, falsy to cancel it.
By default, Chime will log the error and continue the schedule.
Examples
chime
(ns chime-test
(:require
[chime :refer :all]
[clojure.core.async :as a :refer [<! go-loop]]
[clojure.test :refer :all]
[chime.core :as chime]
[chime.joda-time]
[clj-time.core :as t]
[clj-time.periodic])
(:import (java.time Instant)
(java.time.temporal ChronoUnit)))
(deftest test-chime-at
(let [will-be-omitted (.minusSeconds (now) 2)
t1 (.plusSeconds (now) 2)
t2 (.plusSeconds (now) 3)
proof (atom [])]
(chime-at [will-be-omitted t1 t2]
(fn [t]
(swap! proof conj [t
(chime-test/now)])))
(while (not (= (list t1 t2)
(map first @proof))))
(is (= [t1 t2]
(mapv first @proof)))
(check-timeliness! proof)))
(deftest test-error-handler
(testing "continues the schedule"
(let [proof (atom [])
!latch (promise)
sched (chime-at [(.plusMillis (Instant/now) 500)
(.plusMillis (Instant/now) 1000)]
(fn [time]
(throw (ex-info "boom!" {:time time})))
{:error-handler (fn [e]
(swap! proof conj e)
nil)
:on-finished (fn [] (deliver !latch nil))})]
(is (not= ::timeout (deref !latch 1500 ::timeout)))
(is (= 2 (count @proof)))
(is (every? ex-data @proof))))
(testing "rethrowing the error stops the schedule"
(let [proof (atom [])
!latch (promise)
sched (chime-at [(.plusMillis (Instant/now) 500)
(.plusMillis (Instant/now) 1000)]
(fn [time]
(throw (ex-info "boom!" {:time time})))
{:error-handler (fn [e]
(swap! proof conj e)
(throw e))
:on-finished (fn [] (deliver !latch nil))})]
(is (not= ::timeout (deref !latch 1500 ::timeout)))
(is (= 1 (count @proof)))
(is (every? ex-data @proof)))))
(deftest test-on-finished
(let [proof (atom false)]
(chime-at [(.plusSeconds (now) 2) (.plusSeconds (now) 4)]
(deftest test-cancellation-works-even-in-the-face-of-overrun-past-tasks
(let [proof (atom [])
do-stuff (fn [now]
;; some overrunning task:
(swap! proof conj now)
(Thread/sleep 5000))
cancel-stuff! (chime-at (rest (chime/periodic-seq (chime-test/now)
(java.time.Duration/ofSeconds 1)))
do-stuff)]
(Thread/sleep 3000)
(cancel-stuff!)
(is (= 1
(count @proof)))))
(deftest test-empty-or-completely-past-sequences-are-acceptable
(let [proof (atom false)]
(chime-at (map #(.minusSeconds (now) (* 60 %)) [5 4 3 2])
identity
{:on-finished (fn []
(reset! proof true))})
(while (not @proof))
(is @proof))
(let [proof (atom false)]
(chime-at []
identity
{:on-finished (fn []
(reset! proof true))})
(while (not @proof))
(is @proof)))
chime
(ns chime.core-test
(:require [chime.core :as chime]
[clojure.test :as t])
(:import (java.time Instant Duration)
(java.time.temporal ChronoUnit)))
(t/deftest test-chime-at
(let [times [(.minusSeconds (Instant/now) 2)
(.plusSeconds (Instant/now) 1)
(.plusSeconds (Instant/now) 2)]
proof (atom [])]
(with-open [sched (chime/chime-at times
(fn [t]
(swap! proof conj [t (Instant/now)])))]
(Thread/sleep 2500))
(t/is (= times (mapv first @proof)))
(check-timeliness! (rest @proof))))
(t/deftest empty-times
(t/testing "Empty or completely past sequences are acceptable"
(let [proof (atom false)]
(chime/chime-at []
identity
{:on-finished (fn []
(reset! proof true))})
(t/deftest test-on-finished
(let [proof (atom false)]
(chime/chime-at [(.plusMillis (Instant/now) 500) (.plusMillis (Instant/now) 1000)]
(fn [time])
{:on-finished (fn []
(Thread/sleep 100)
(reset! proof true))})
(Thread/sleep 1200)
(t/is @proof)))
(t/deftest test-error-handler
(t/testing "returning true continues the schedule"
(let [proof (atom [])
sched (chime/chime-at [(.plusMillis (Instant/now) 500)
(.plusMillis (Instant/now) 1000)]
(fn [time]
(throw (ex-info "boom!" {:time time})))
{:error-handler (fn [e]
(swap! proof conj e)
true)})]
(t/is (not= ::timeout (deref sched 1500 ::timeout)))
(t/is (= 2 (count @proof)))
(t/is (every? ex-data @proof))))
(t/testing "returning false stops the schedule"
(let [proof (atom [])
sched (chime/chime-at [(.plusMillis (Instant/now) 500)
(.plusMillis (Instant/now) 1000)]
(fn [time]
(throw (ex-info "boom!" {:time time})))
{:error-handler (fn [e]
(swap! proof conj e)
false)})]
(t/is (not= ::timeout (deref sched 1500 ::timeout)))
(t/is (= 1 (count @proof)))
(t/is (every? ex-data @proof)))))
(t/deftest test-long-running-jobs
(let [proof (atom [])
!latch (promise)
now (Instant/now)
times (->> (chime/periodic-seq now (Duration/ofMillis 500))
(take 3))
sched (chime/chime-at times
(fn [time]
(swap! proof conj [time (Instant/now)])
(Thread/sleep 750)))]
(t/deftest test-cancelling-overrunning-task
(let [!proof (atom [])
!error (atom nil)
!latch (promise)]
(with-open [sched (chime/chime-at (chime/periodic-seq (Instant/now) (Duration/ofSeconds 1))
(fn [now]
(swap! !proof conj now)
(Thread/sleep 3000))
{:error-handler (fn [e]
(reset! !error e))
:on-finished (fn []
(deliver !latch nil))})]
(Thread/sleep 2000))
(t/deftest test-only-call-on-finished-once-36
(let [!count (atom 0)
now (Instant/now)]
(with-open [chiming (chime/chime-at [(.plusMillis now 500)
(.plusMillis now 500)]
(fn [time])
{:on-finished #(swap! !count inc)})]
(Thread/sleep 1000))