Skip to content

Commit

Permalink
Clarifying behaviour around Errors, #31
Browse files Browse the repository at this point in the history
  • Loading branch information
jarohen committed Aug 17, 2020
1 parent 9a33f05 commit 68a8b7f
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 26 deletions.
14 changes: 8 additions & 6 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,23 @@ Instead, complex schedules can be expressed with liberal use of standard Clojure
(map #(nth % 2))))
----

=== Error handling
=== Exception handling

You can pass an error-handler to `chime-at` - a function that takes the exception as an argument.
You can pass an exception-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.
By default, Chime will log the exception and continue the schedule.

[source,clojure]
----
(chime-at [times...]
do-task-fn
{:error-handler (fn [e]
;; log, alert, notify etc?
)})
{:exception-handler (fn [e]
;; log, alert, notify etc?
)})
----

If the schedule function throws anything that isn't an `Exception`, the schedule terminates.

=== `core.async`
If you already have Clojure's core.async in your project, you may prefer `chime.core-async/chime-ch`

Expand Down
28 changes: 18 additions & 10 deletions src/chime/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,25 @@
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.
You can pass an exception-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.
By default, Chime will log the exception and continue the schedule.
"
(^java.lang.AutoCloseable [times f] (chime-at times f {}))

(^java.lang.AutoCloseable [times f {:keys [error-handler on-finished]}]
(^java.lang.AutoCloseable [times f {:keys [exception-handler on-finished] :as opts}]
(let [pool (Executors/newSingleThreadScheduledExecutor thread-factory)
!latch (promise)
error-handler (or error-handler
(fn [e]
(log/warn e "Error running scheduled fn")
(not (instance? InterruptedException e))))]
exception-handler (or exception-handler
(when-let [error-handler (:error-handler opts)]
(defonce __error-handler-warn
(log/warn "`:error-handler` is deprecated - rename to `:exception-handler`"))
error-handler)
(fn [e]
(let [interrupted? (instance? InterruptedException e)]
(when-not interrupted?
(log/warn e "Error running scheduled fn"))
(not interrupted?))))]
(letfn [(close []
(.shutdownNow pool)
(deliver !latch nil)
Expand All @@ -92,9 +98,11 @@
true
(catch Exception e
(try
(error-handler e)
(catch Exception e
(log/error e "error calling chime error-handler, stopping schedule")))))
(exception-handler e)
(catch Throwable e
(log/error e "Error calling Chime exception-handler, stopping schedule"))))
(catch Throwable t
(log/error t (str (class t) " thrown, stopping schedule"))))

(schedule-loop times)
(close)))]
Expand Down
41 changes: 31 additions & 10 deletions test/chime/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@
(Thread/sleep 1200)
(t/is @proof)))

(t/deftest test-error-handler
(t/deftest test-exception-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)})]
{:exception-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))))
Expand All @@ -66,12 +66,33 @@
(.plusMillis (Instant/now) 1000)]
(fn [time]
(throw (ex-info "boom!" {:time time})))
{:error-handler (fn [e]
(swap! proof conj e)
false)})]
{:exception-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/is (every? ex-data @proof))))

(t/testing "no exception handler continues the schedule"
(let [proof (atom [])
sched (chime/chime-at [(.plusMillis (Instant/now) 500)
(.plusMillis (Instant/now) 1000)]
(fn [time]
(swap! proof conj :run)
(throw (ex-info "boom!" {:time time}))))]
(t/is (not= ::timeout (deref sched 1500 ::timeout)))
(t/is (= [:run :run] @proof)))))

(t/deftest test-throwing-error-cancels-schedule
(let [proof (atom [])
sched (chime/chime-at [(.plusMillis (Instant/now) 500)
(.plusMillis (Instant/now) 1000)
(.plusMillis (Instant/now) 1500)]
(fn [time]
(when (> (count (swap! proof conj :run)) 1)
(throw (Error. "test-error")))))]
(t/is (not= ::timeout (deref sched 1200 ::timeout)))
(t/is (= [:run :run] @proof))))

(t/deftest test-long-running-jobs
(let [proof (atom [])
Expand Down Expand Up @@ -99,8 +120,8 @@
(fn [now]
(swap! !proof conj now)
(Thread/sleep 3000))
{:error-handler (fn [e]
(reset! !error e))
{:exception-handler (fn [e]
(reset! !error e))
:on-finished (fn []
(deliver !latch nil))})]
(Thread/sleep 2000))
Expand Down

0 comments on commit 68a8b7f

Please sign in to comment.