tolitius / mount

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

Throwing exception when accessing unstarted state in cljs #95

Open fmnasution opened 6 years ago

fmnasution commented 6 years ago

Is there anyway to do that? Right now accessing unstarted state in cljs will forcefully start the state, this will make the given state might start more than once and possibly in the wrong order.

tolitius commented 6 years ago

the idea behind starting the state at @ is to avoid null pointers and enable starting only those components that are used.

this will make the given state might start more than once and possibly in the wrong order

can you provide an example?

fmnasution commented 6 years ago

Hi, sorry for the late reply!

Steps to reproduce:

  1. Clone https://github.com/fmnasution/lotus
  2. Start repl
  3. Call (start!)
  4. Open localhost:8080

You will see in the console log that several states is started more than once and possibly in the wrong order. In my case it's #'lotus.router.html-router, #'lotus.element.element, and #'lotus.ajax.ring-ajax.

tolitius commented 6 years ago

started several times, states started in exactly the same order:

{:started
 ["#'lotus.command/event-dispatcher"
  "#'lotus.command/effect-dispatcher"
  "#'lotus.config/config"
  "#'lotus.datastore/datastore-conn"
  "#'lotus.ws/ws-remote"
  "#'lotus.command.listener/event-listener"
  "#'lotus.command.listener/effect-listener"
  "#'lotus.datastore.bootstrapper/datastore-conn-bootstrapper"
  "#'lotus.datastore.watcher/datastore-conn-watcher"
  "#'lotus.datastore.listener/datastore-conn-listener"
  "#'lotus.ws.listener/ws-listener"
  "#'lotus.router/ring-middleware"
  "#'lotus.router/ring-router"
  "#'lotus.web/web-server"
  "#'lotus.figwheel/figwheel-server"]}

you could also use mount.tools.graph to see deps and order:

=> (require '[mount.tools.graph :as graph])
=> (graph/states-with-deps)

({:name "#'lotus.command/event-dispatcher",
  :order 1,
  :status #{:started},
  :deps #{}}
 {:name "#'lotus.command/effect-dispatcher",
  :order 2,
  :status #{:started},
  :deps #{}}
 {:name "#'lotus.config/config",
  :order 3,
  :status #{:started},
  :deps #{}}
 {:name "#'lotus.datastore/datastore-conn",
  :order 4,
  :status #{:started},
  :deps #{"#'lotus.config/config"}}
 {:name "#'lotus.ws/ws-remote",
  :order 5,
  :status #{:started},
  :deps #{}}
 {:name "#'lotus.command.listener/event-listener",
  :order 6,
  :status #{:started},
  :deps
  #{"#'lotus.ws/ws-remote" "#'lotus.datastore/datastore-conn"
    "#'lotus.command/effect-dispatcher" "#'lotus.config/config"
    "#'lotus.command/event-dispatcher"}}
 {:name "#'lotus.command.listener/effect-listener",
  :order 7,
  :status #{:started},
  :deps
  #{"#'lotus.ws/ws-remote" "#'lotus.datastore/datastore-conn"
    "#'lotus.command/effect-dispatcher" "#'lotus.config/config"
    "#'lotus.command/event-dispatcher"}}
 {:name "#'lotus.datastore.bootstrapper/datastore-conn-bootstrapper",
  :order 8,
  :status #{:started},
  :deps #{"#'lotus.datastore/datastore-conn"}}
 {:name "#'lotus.datastore.watcher/datastore-conn-watcher",
  :order 9,
  :status #{:started},
  :deps
  #{"#'lotus.datastore/datastore-conn"
    "#'lotus.datastore.bootstrapper/datastore-conn-bootstrapper"}}
 {:name "#'lotus.datastore.listener/datastore-conn-listener",
  :order 10,
  :status #{:started},
  :deps
  #{"#'lotus.command/event-dispatcher"
    "#'lotus.datastore.watcher/datastore-conn-watcher"}}
 {:name "#'lotus.ws.listener/ws-listener",
  :order 11,
  :status #{:started},
  :deps #{"#'lotus.ws/ws-remote" "#'lotus.command/event-dispatcher"}}
 {:name "#'lotus.router/ring-middleware",
  :order 12,
  :status #{:started},
  :deps
  #{"#'lotus.ws/ws-remote" "#'lotus.datastore/datastore-conn"
    "#'lotus.config/config"}}
 {:name "#'lotus.router/ring-router",
  :order 13,
  :status #{:started},
  :deps
  #{"#'lotus.ws/ws-remote" "#'lotus.datastore/datastore-conn"
    "#'lotus.config/config"}}
 {:name "#'lotus.web/web-server",
  :order 14,
  :status #{:started},
  :deps #{"#'lotus.router/ring-router" "#'lotus.config/config"}}
 {:name "#'lotus.figwheel/figwheel-server",
  :order 15,
  :status #{:started},
  :deps #{"#'lotus.config/config"}})

is it not what you see?

tolitius commented 6 years ago

as to cljs, can you narrow it down to, say, two specific states that start out of order and provide a bit more details on:

since this is not a simple app and it would take time to debug and understand why/how and what it does.

fmnasution commented 6 years ago

Hi tolitius, thanks for the reply!

When it comes to clj the states are working fine. They start once and in order. The problem is in cljs where some states would start multiple times and in the wrong order. The problematic states for me is: #'lotus.router.html-router, #'lotus.element.element, and #'lotus.ajax.ring-ajax.

I actually have found a workaround for this. Most of my states basically has "async" property in it, meaning it accept a callback to start and in the callback it calls another state like:


(defn start-a!
  [callback-manager]
  (actually-start-a! (fn [data]
                       (act-on-data callback-manager data))))

(defstate b
  :start (start-b!))

(defstate a
  :start (start-a! @b))

This becomes a problem where it is actually calling the callback right at the start (Like #'lotus.router.html-router, it is basically a wrapper around bidi.router/start-router! ). The solution is to defer the calling of the dependency, in this case it's b.


(defn start-a!
  [callback-manager]
  (actually-start-a! (fn [data]
                       (act-on-data @callback-manager data))))

(defstate b
  :start (start-b!))

(defstate a
  :start (start-a! b))

I think this is one of those "gotchas" where a state shouldn't be accessed asynchronously.