mount icon indicating copy to clipboard operation
mount copied to clipboard

A reset function which will reset all the states even if there are errors.

Open sdave2 opened this issue 8 years ago • 2 comments

Situation I ran into:

This is interesting. I defined a var with a defstate but made a mistake the first time around.

;; not the same mistake but it will help elaborate what I’m trying to explain

(def conn1-atom (atom nil))
(defstate conn
  :start (reset! conn1-atom :started)
  :stop (reset! :started conn1-atom))  ;; there is a mistake in :stop.

(mount/start) ;; this works fine. (mount/stop) ;; produces the error.

ClassCastException clojure.lang.Keyword cannot be cast to clojure.lang.IAtom clojure.core/reset! (core.clj:2273)

;; trying to re-define conn but keep running into the same error.

(defstate conn
  :start (reset! conn1-atom :started)
  :stop (reset! conn1-atom :stopped))

A function to reset all the states/connections even if they produce error would be nice. This was mentioned on Slack's mount channel by @tolitius . "it might make sense to potentially have a some kind of (mount/reset) that could take params: i.e. (mount/reset :ignore-errors true). which, if well documented, could help in situations like this one."

sdave2 avatar Sep 22 '16 14:09 sdave2

To summarize the discussion with @tolitius:

  • mount/reset makes sense as a dev-time tool. The reset would stop all started states unconditionally and clear the internal Mount state.

The only open question is how to deal with states which fail to stop cleanly:

  • we don't want to change the semantics of stop which stops the STOP operation on the first exception
  • one option is for reset to return a set of Vars which failed to stop cleanly so that e.g. the dangling connection can still be forced closed

dm3 avatar Mar 09 '17 08:03 dm3

After thinking a bit more a separate API such as reset is unnecessary, a call to (mount/stop) should handle this use case.

Pushed changes to 0.1.12-SNAPSHOT. A call to (mount/stop) on failure to stop a state will report it and mark the state as :stopped. This would allow to:

  • stop all the subsequent states
  • manually handle states that could not stop in REPL
  • redefine states "stop" functions in the same REPL session if needed

(mount/stop) will return all states that were successfully stopped.

boot.user=> (defstate a :start 42 :stop (throw (RuntimeException. "BOOM")))
#'boot.user/a
boot.user=> (defstate b :start 1 :stop 2)
#'boot.user/b
boot.user=> (mount/start)
{:started ["#'boot.user/a" "#'boot.user/b"]}
boot.user=> a
42
boot.user=> b
1
boot.user=> (mount/stop)
#error {
 :cause "BOOM"
 :via
 [{:type clojure.lang.ExceptionInfo
   :message "could not stop [#'boot.user/a] due to"
   ...}]}
{:stopped ["#'boot.user/b"]}

Since a state could not be stopped, it still points to its original value after the "stop" function was called:

boot.user=> a
42
boot.user=> b
#object[mount.core.NotStartedState 0x2156abe2 "'#'boot.user/b' is not started (to start all the states call mount/start)"]

However from mount's perspective a is :stopped and can be started / redefined again. This allows a manual "force stop" of a in REPL.

We can now redefine a's stop function to "fix" the exception:

boot.user=> (defstate a :start 42 :stop 34)
#'boot.user/a
boot.user=> (mount/start)
{:started ["#'boot.user/a" "#'boot.user/b"]}
boot.user=> a
42
boot.user=> b
1
boot.user=> (mount/stop)
{:stopped ["#'boot.user/b" "#'boot.user/a"]}
boot.user=> a
#object[mount.core.NotStartedState 0xa04c91c "'#'boot.user/a' is not started (to start all the states call mount/start)"]
boot.user=> b
#object[mount.core.NotStartedState 0x56b0c866 "'#'boot.user/b' is not started (to start all the states call mount/start)"]

tolitius avatar Mar 13 '17 03:03 tolitius