tolitius / mount

managing Clojure and ClojureScript app state since (reset)
Eclipse Public License 1.0
1.22k stars 88 forks source link

Is there a way to know the state of a state, started or stopped? #66

Closed pupeno closed 7 years ago

tolitius commented 7 years ago

It would depend on what / where this question needs to be answered. Here is why:

Tale of Modes

mount has two modes clj and cljc.

In the default, clj mode, the real instance of a started state is the state itself. For example:

boot.user=> (defstate a :start 42)
#'boot.user/a

boot.user=> (type a)
mount.core.DerefableState

boot.user=> (mount/start #'boot.user/a)
{:started ["#'boot.user/a"]}

boot.user=> (type a)
java.lang.Long

notice that after the start is called a becomes 42 (i.e. java.util.Long).

Whereas in cljc mode all states are derefable even when started, which means the real instance (whether started or stopped) is hiding behind the @:

boot.user=> (mount/in-cljc-mode)
:cljc
boot.user=> (defstate a :start 42)
#'boot.user/a
boot.user=>

boot.user=> (type a)
mount.core.DerefableState
boot.user=>

boot.user=> (mount/start #'boot.user/a)
{:started ["#'boot.user/a"]}
boot.user=>

boot.user=> (type a)
mount.core.DerefableState
boot.user=>

boot.user=> (type @a)
java.lang.Long

notice how a is still derefable even after it is started.

Meaning of Started and Stopped

Let's say we have a database connection that we need to check the start/stop status for:

boot.user=> (defn connect [{:keys [uri]}] {:connected uri})
#'boot.user/connect
boot.user=> (defn disconnect [{:keys [connected-to]}] (println "disconnected from:" connected-to))
#'boot.user/disconnect

boot.user=> (def config {:db {:uri "postgresql://localhost:5432/whatsapp"}})
#'boot.user/config

boot.user=> (defstate db :start (connect (config :db))
                         :stop (disconnect db))
#'boot.user/db

Until it's started it is "not going to be connected":

boot.user=> (:connected db)
nil

once it is started, it will be "connected":

boot.user=> (mount/start #'boot.user/db)
{:started ["#'boot.user/db"]}

boot.user=> (:connected db)
"postgresql://localhost:5432/whatsapp"

notice however that it is just how we designed the state: i.e. :connected comes from the connect function and gets to live within the db state.

(!) The interesting bit here is the true runtime state of the database connection: if the connection gets closed at runtime due to, say, a network glitch, the (:connected db) will still return true.

Checking by the state name

running states

You can always check the state by its fully qualified name via (mount/running-states) which returns a set of all the states started by mount, although I find it only useful for debugging purposes:

boot.user=> (defstate db :start (connect (config :db)) :stop (disconnect db))
#'boot.user/db

boot.user=> ((mount/running-states) "#'boot.user/db")
nil

boot.user=> (mount/start #'boot.user/db)
{:started ["#'boot.user/db"]}

boot.user=> ((mount/running-states) "#'boot.user/db")
"#'boot.user/db"

app graph

Or you can search in the graph by the state name (again, usually useful mostly for monitoring / debugging):

boot.user=> (defstate db :start (connect (config :db))
                         :stop (disconnect db))
#'boot.user/db
boot.user=> (mount/start #'boot.user/db)
{:started ["#'boot.user/db"]}

boot.user=> (defstate a :start 42)
#'boot.user/a
boot.user=> (defstate b :start 34)
#'boot.user/b

boot.user=> (require '[mount.tools.graph :as graph])

boot.user=> (graph/states-with-deps)
({:name "#'boot.user/db", :order 2, :status #{:started}, :deps #{}}
 {:name "#'boot.user/a", :order 3, :status #{:stopped}, :deps #{}}
 {:name "#'boot.user/b", :order 4, :status #{:stopped}, :deps #{}})

(mount/start) does it

But in practice, if (mount/start) is called all of the states will be started (unless there are errors in one of the start functions).

Another interesting property of cljc mode, is that states can be started lazily:

boot.user=> (mount/in-cljc-mode)
:cljc
boot.user=> (defstate a :start 41)
#'boot.user/a
boot.user=> (+ 1 @a)
42

this worked without calling (mount/start), since @ will start the state if it was not already started.

tolitius commented 7 years ago

closing. feel free to reopen in case more clarification is needed.