tolitius / mount Goto Github PK
View Code? Open in Web Editor NEWmanaging Clojure and ClojureScript app state since (reset)
License: Eclipse Public License 1.0
managing Clojure and ClojureScript app state since (reset)
License: Eclipse Public License 1.0
The second bug is not easy to understand for me. Also I am not sure if it is mounts fault or not, it would be nice if you could have a look at it.
To reproduce:
You will see this stacktrace in the repl now:
"<< stopping.. #'foo.bar.components.server/server (namespace was recompiled)"
">> starting.. #'foo.bar.components.server/server (namespace was recompiled)"
Wed Dec 23 08:14:35 CET 2015 [worker-1] ERROR - GET /example
java.lang.RuntimeException: could not start [#'foo.bar.components.server/server] due to, compiling:(foo/bar/components/server.clj:31:1)
at clojure.lang.Compiler.load(Compiler.java:7239)
at clojure.lang.RT.loadResourceScript(RT.java:371)
at clojure.lang.RT.loadResourceScript(RT.java:362)
at clojure.lang.RT.load(RT.java:446)
at clojure.lang.RT.load(RT.java:412)
at clojure.core$load$fn__5448.invoke(core.clj:5866)
at clojure.core$load.doInvoke(core.clj:5865)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5671)
at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
at clojure.core$load_lib.doInvoke(core.clj:5710)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invoke(core.clj:632)
at clojure.core$load_libs.doInvoke(core.clj:5749)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:632)
at clojure.core$require.doInvoke(core.clj:5832)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at ring.middleware.reload$wrap_reload$fn__25452.invoke(reload.clj:21)
at clojure_miniprofiler$run_handler_profiled$fn__20024.invoke(clojure_miniprofiler.clj:382)
at clojure_miniprofiler$run_handler_profiled.invoke(clojure_miniprofiler.clj:382)
at clojure_miniprofiler$wrap_miniprofiler$fn__20035.invoke(clojure_miniprofiler.clj:429)
at noir.util.middleware$wrap_request_map$fn__17803.invoke(middleware.clj:39)
at ring.middleware.keyword_params$wrap_keyword_params$fn__16572.invoke(keyword_params.clj:35)
at ring.middleware.nested_params$wrap_nested_params$fn__16622.invoke(nested_params.clj:84)
at ring.middleware.multipart_params$wrap_multipart_params$fn__16749.invoke(multipart_params.clj:117)
at ring.middleware.params$wrap_params$fn__16775.invoke(params.clj:64)
at ring.middleware.cookies$wrap_cookies$fn__16359.invoke(cookies.clj:161)
at ring.middleware.absolute_redirects$wrap_absolute_redirects$fn__16903.invoke(absolute_redirects.clj:36)
at ring.middleware.resource$wrap_resource$fn__16811.invoke(resource.clj:28)
at ring.middleware.content_type$wrap_content_type$fn__16880.invoke(content_type.clj:30)
at ring.middleware.not_modified$wrap_not_modified$fn__16861.invoke(not_modified.clj:52)
at ring.middleware.x_headers$wrap_xss_protection$fn__16211.invoke(x_headers.clj:71)
at ring.middleware.x_headers$wrap_frame_options$fn__16201.invoke(x_headers.clj:38)
at ring.middleware.x_headers$wrap_content_type_options$fn__16206.invoke(x_headers.clj:53)
at hiccup.middleware$wrap_base_url$fn__17134.invoke(middleware.clj:12)
at ring.middleware.format_params$wrap_format_params$fn__17596.invoke(format_params.clj:113)
at ring.middleware.format_params$wrap_format_params$fn__17596.invoke(format_params.clj:113)
at ring.middleware.format_params$wrap_format_params$fn__17596.invoke(format_params.clj:113)
at ring.middleware.format_response$wrap_format_response$fn__17725.invoke(format_response.clj:174)
at noir.validation$wrap_noir_validation$fn__17184.invoke(validation.clj:155)
at noir.cookies$noir_cookies$fn__17245.invoke(cookies.clj:72)
at ring.middleware.cookies$wrap_cookies$fn__16359.invoke(cookies.clj:161)
at noir.session$noir_flash$fn__17328.invoke(session.clj:158)
at ring.middleware.flash$wrap_flash$fn__16232.invoke(flash.clj:35)
at noir.session$noir_session$fn__17314.invoke(session.clj:109)
at ring.middleware.session_timeout$wrap_idle_session_timeout$fn__17270.invoke(session_timeout.clj:25)
at ring.middleware.session$wrap_session$fn__16480.invoke(session.clj:102)
at ring.middleware.file$wrap_file$fn__16832.invoke(file.clj:44)
at ring.middleware.file_info$wrap_file_info$fn__17860.invoke(file_info.clj:69)
at compojure.core$routing$fn__16050.invoke(core.clj:144)
at clojure.core$some.invoke(core.clj:2570)
at compojure.core$routing.doInvoke(core.clj:144)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:632)
at compojure.core$routes$fn__16054.invoke(core.clj:149)
at org.httpkit.server.HttpHandler.run(RingHandler.java:91)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.RuntimeException: could not start [#'foo.bar.components.server/server] due to
at mount.core$up$fn__2246.invoke(core.cljc:88)
at mount.core$up.invoke(core.cljc:88)
at clojure.lang.Var.invoke(Var.java:388)
at foo.bar.components.server$eval36438.invoke(server.clj:31)
at clojure.lang.Compiler.eval(Compiler.java:6782)
at clojure.lang.Compiler.eval(Compiler.java:6772)
at clojure.lang.Compiler.load(Compiler.java:7227)
... 61 more
Caused by: java.lang.ClassCastException: mount.core.DerefableState cannot be cast to clojure.lang.IFn
at org.httpkit.server$run_server.invoke(server.clj:8)
at foo.bar.components.server$eval36438$fn__36439.invoke(server.clj:31)
at mount.core$record_BANG_.invoke(core.cljc:82)
at mount.core$up$fn__2246.invoke(core.cljc:91)
... 67 more
The interesting namespaces regarding mount are:
foo.bar.user
and the component definitions are in:
foo.bar.components.*
Is there a way to have different system configurations (based on command line arguments or config etc.)? The way I'm using component in a large app is that I have a "server" system and a "cli" system. The "cli" system doesn't have all bells and whistles of the "server" system (e.g. it doesn't need the web server) it uses a different logging subsystem etc. Is there a way to do that with your library?
Note that they don't exist at the same time.
[mount "0.1.10"]
[org.clojure/clojure "1.9.0-alpha14"]
(mount.core/defstate do-not-start-me
:start (println "Darn. I started."))
(mount.core/start (mount/only #{}))
Nothing is printed.
Darn. I started.
is printed.
First of all, mount seems a great idea. I am currently refactoring a stuartsierra/component app to mount's defstates, in order to evaluate it. I like how stuff becomes more clojuresque. What I am missing though are two things (also mentioned in the following issues: #2 #8 #9)
with-args
approach, binding them to a single var/atom in the mount.core
namespace.start-with
, start-without
, but those do not compose. And even if they did, the required nesting/composition is not really programmatically configurable.So, I was thinking, what about a single start function, taking a map for mix-n-matching the available options? This also makes mount more extendible with new features, without the need to update or add new API functions.
For example, what about this?
(mount/start
{;; Only start the following state vars, replacing `(start <state>+)`
:only [#'my.mount.config/config]
;; Or, start all known state vars, except the following,
;; replacing `(start-without ...)`
:except [#'my.mount.db.datomic/connection]
;; Substitute some states' start and stop functions with those of
;; other states, replacing `(start-with {...})`
:substitute-with-var {#'my.mount.config/config #'my.mount.config.map/data}
;; and/or substitute some states with fixed values (new feature)
:substitute-with-val {#'my.mount.config/config {:my 'config-data}}
;; and/or substitute some states' start and stop functions with
;; those specified here directly (new feature)
:substitute-with-state {#'my.mount.config/config {:start {:my 'config-data}}}
;; Specify arguments for state start functions, expecting those
;; functions to have (at least) a 1-arity version.
:arguments {#'my.mount.config/config {:resource "/config.edn"}
#'my.mount.db.datomic/connection {:config-var #'my.mount.config/config}
#'my.mount.config.map/data {:my 'config-data}}})
This composes all options nicely, and the data structure can be passed around, read from a file and/or altered with Clojure's core functions (as advocated in https://www.youtube.com/watch?v=3oQTSP4FngY). I think this approach opens up the possibility of composing the modules better, enabling this "standard library" approach.
Of course, if you still want to offer a more function-based API, one could add "builder" functions, such as:
(-> (mount/with {#'my.mount.config/config #'my.mount.config.map/data})
(mount/without #'my.mount.db.datomic/connection)
(mount/with-args {#'my.mount.config/config {:resource "/config.edn"}})
(mount/start))
I am very interested in your opinion, and if you like it, I'll try to find the time to write the actual code for this idea.
Hi Anatoly,
After compiling some cljs using :advanced mode I happened to look in the minified file and saw that where I call defstate
the output includes the full path of the file. For example, I end up with code like this:
ff([bv, S, ow, Gw, Zz, GC, mF, Xg, eJ, pK],
[IF, KG, "/Users/tom/dev/kc/src/admin/client/components/app_state.cljs", 20, 1, 103, 103, ue, null , k(U6) ? U6.Kb : null])
I'm fairly certain mount is the culprit because I only see this occurring for files with a defstate
. Aside from the fact that I'd like to conceal from my users that I develop on a mac, it seems wasteful to include all that in the minified script. I haven't been able to hunt down the offending lines, so could you point me in the right direction?
Hi,
I've been reading the documentation but I'm not sure about how to fix this. I've defined something like this:
(defstate server-config :start {:port 7890 :join? false})
(defn start-server [server-config]
(when-let [server (run-server myserv-ring-handler server-config)]
(println "Server has started!")
server))
(defstate myserv-server :start (start-server server-config)
:stop (myserv-server :timeout 100))
(defn system-port [args]
(Integer/parseInt
(or (System/getenv "PORT")
(first args)
"7890")))
(defn -main [& args]
(mount/start-with-states
{#'myserv/server-config
{:start #(array-map :port (system-port args)
:join? false)}}))
So when I "lein run" everything works, but whenever I change a file, and the http-kit server is stopped, the command stops. For the moment I'm doing "while true; do lein run; done" to work, so I've thought about adding an infinite loop to the -main function, but it doesn't feel like this is the right way.
How should I do this?
So when I edit some namespace with defstate
in it, refresh
destroys it completely. Which leads to port is already in use
for instance. More info: https://github.com/clojure/tools.namespace#reloading-code-preparing-your-application. Don't know how to solve this.
Be nice to have a status command or something like that.
Hi - apologies if this isn't the place for support ;-).
One pain I feel all the time with these 'state/dependency-management' libs is the conflict between dynamically defining components and producing WARs which require the ring handlers to be a simple def
.
In Stuarts' Component I simply have the handler pull the components it needs directly out of the #'system
.
Is there a more elegant answer with mount?
Specifically, what would a web UI look like on top of your demo app which exposes the nyse-finder via a HTTP endpoint. Is it really just a simple as ring's :init
method pointing to something which calls (mount/start)
and the handlers then just require
the nyse
namespace?
Thanks!
[org.clojure/clojure "1.8.0"]
[mount "0.1.10"]
(defn create [name from]
(let [id (gensym)]
(println "creating" name id "from" from)
{:name name
:id id}))
(defn destroy [state from]
(println "destroying" (:name state) (:id state) "from" from))
(defstate a :start (create "a" "start a")
:stop (destroy a "stop a"))
(defstate b :start (create "b" "start b")
:stop (destroy b "stop b"))
(defn info []
(println "current states:")
(println " " (mount.tools.graph/states-with-deps))
(println " a:" (:name a) (:id a))
(println " b:" (:name b) (:id b)))
(defn this-works-as-expected []
(mount/start)
(info)
(mount/stop))
(defn this-is-not-stopping-b []
(mount/start-with-states {#'user/a #'user/b})
(info)
(mount/stop))
no surprises when calling (this-works-as-expected)
user=> (this-works-as-expected)
creating a G__12960 from start a
creating b G__12961 from start b
current states:
({:name #'user/a, :order 1, :status #{:started}, :deps #{}} {:name #'user/b, :order 2, :status #{:started}, :deps #{}})
a: a G__12960
b: b G__12961
destroying b G__12961 from stop b
destroying a G__12960 from stop a
{:stopped ["#'user/b" "#'user/a"]}
but calling (this-is-not-stopping-b)
does not properly stop the substitute
user=> (this-is-not-stopping-b)
creating b G__13029 from start b
current states:
({:name #'user/a, :order 1, :status #{:started}, :deps #{}} {:name #'user/b, :order 2, :status #{:stopped}, :deps #{}})
a: b G__13029
b: nil nil
destroying nil nil from stop b
{:stopped ["#'user/a"]}
user=> (this-is-not-stopping-b)
...
destroying b G__13029 from stop b
user=> (this-is-not-stopping-b)
...
destroying nil nil from stop b
When I evaluate (in CIDER, for example) running state, mount calls it's stop
function. I believe it is connected to #18
Hi,
I was trying out 0.1.8-Snapshot as suggested and things have become better. But still I found two bugs.
The first one is that I have .cljc files in use. The code is used on clojure and clojurescript side, whenever I change the .cljc code my server component stops and does not get restarted.
I setup an example project (it is rather large, but hopefully it can help despite the noise): https://github.com/sveri/mountexample.
To reproduce:
lein figwheel
lein repl
(start)
in the replfoo.bar.closp-schema
The interesting namespaces regarding mount are:
foo.bar.user
and the component definitions are in:
foo.bar.components.*
It will be very consistent to manage application state the same way for Clojure and ClojureScript, isn't it?
When I tried to use a plain value like a map as the start
form, an error is thrown:
boot.user=> (defstate config :start {:init :state})
nil
boot.user=> (start)
21:21:30.007 [nREPL-worker-16] INFO mount.core - >> starting.. config
java.lang.RuntimeException: could not start [config] due to
clojure.lang.ArityException: Wrong number of args (0) passed to: PersistentVector
In defstate, should this
:start `(fn [] (~@start))
be changed to this?
:start `(fn [] ~start)
Things defined with defstate
start themselves, I think due to some sort of side-effect of printing.
Example:
Given this file:
(ns foo.core
(:require [mount.core :refer [defstate]]))
(defn- start-fn []
(println "Launching missiles!")
(+ 40 2))
(defstate blah
:start (start-fn))
I started a repl (nrepl, in emacs, via leiningen), evaluated the namespace, then simply evaluated blah
twice.
foo.core> blah
Launching missiles!
#mount.core.DerefableState[{:status :ready, :val #object[mount.core.NotStartedState 0x20ae4d21 "'#'foo.core/blah' is not started (to start all the states call mount/start)"]} 0x199b0da2]
foo.core> blah
42
Note that I never called mount.core/start
, but blah
is now started.
So a defstate seems to create some sort of Heisenvar, such that simply looking at it changes its value.
boot.user=> (start-repl)
cljs.user=> (require-macros '[mount.core :refer [defstate]])
nil
cljs.user=> (defstate a :start 42)
clojure.lang.ExceptionInfo: Unable to resolve var: cleanup-if-dirty in this context at line 1 <cljs repl> {:file "<cljs repl>", :line 1, :column 1, :tag :cljs/analysis-error}
cljs.user=>
cljs.user=> (require '[mount.core :as mount])
nil
cljs.user=> (mount/in-cljc-mode)
:cljc
cljs.user=> (defstate a :start 42)
#'cljs.user/a
cljs.user=> a
#object[mount.core.DerefableState]
cljs.user=> @a
42
thanks to @frankhenderson for reporting this. his sample project that confirms the behavior.
The clojars repository has the following file:
https://clojars.org/repo/mount/mount/maven-metadata.xml
It has a bug in it that makes lein-ancient fail. It references a version (0.2.0-SNAPSHOT) that does not exist in clojars.org or maven.org and therefore causes lein-ancient to fail to pick up the latest snapshot of mount (currently 0.1.8-SNAPSHOT).
here is the relevant section of the file:
0.1.0-SNAPSHOT
0.1.0
0.2.0-SNAPSHOT ;; <- delete this line
0.1.2
0.1.3-SNAPSHOT
0.1.1
mount v 0.1.7 only works with Clojure >= 1.7
This is not documented anywhere that I can see.
See this reddit post: https://www.reddit.com/r/Clojure/comments/3y8qc6/im_having_troubles_requiring_a_library/
Recently ran into a silly problem where I passed mount/stop a vector of states and none of the states stopped. Was a bit of a surprise to me because mount/start accepts a collection.
Not that it's a big deal, but wondering if there's any reason for a non-congruent API.
I noticed that if I delete a defstate and then do a clojure.tools.namespace.repl/refresh, calling (mount/start) still reports that the old defstate is being started even though it is not. I have to restart the repl to get (mount/start) to report what is actually happening.
Hi,
I'm not sure if I'm missing the point here, but I'm having trouble with things not being started at the point when I need them. For example, when I have 2 files...
(ns oddity.core
(:require [mount :refer (defstate)]))
(println "Loading core")
(defn open-a-thing []
"hello world")
(defstate thing
:start (open-a-thing))
and
(ns oddity.thing-user
(:require [oddity.core :refer (thing)]))
(println "Opening thing_user")
(println thing)
Then I get the following
user> (dev)
#object[clojure.lang.Namespace 0x2ce92a78 "dev"]
dev> go
#object[dev$go 0x50c9780d "dev$go@50c9780d"]
dev> (go)
:ready
dev> (reset)
:reloading (oddity.core oddity.thing-user oddity.core-test dev)
#object[mount.NotStartedState 0x1d2642c9 'thing' is not started (to start all the states call mount/start)]
:ready
Here's my project
(defproject oddity "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.7.0"]
[mount "0.1.2"]]
:profiles {:dev {:dependencies [[org.clojure/tools.namespace "0.2.11"]]
:source-paths ["dev"]}})
My dev/dev.clj is a modified version of the nyse example
(ns dev
"Tools for interactive development with the REPL. This file should
not be included in a production build of the application."
;; (:use [cljs.repl :only [repl]]
;; [cljs.repl.browser :only [repl-env]])
(:require [clojure.java.io :as io]
[clojure.java.javadoc :refer [javadoc]]
[clojure.pprint :refer [pprint]]
[clojure.reflect :refer [reflect]]
[clojure.repl :refer [apropos dir doc find-doc pst source]]
[clojure.set :as set]
[clojure.string :as str]
[clojure.test :as test]
;; [clojure.core.async :refer [>!! <!! >! <! go-loop alt! timeout]]
[clojure.tools.namespace.repl :as tn]
[mount]))
(defn start []
(mount/start))
(defn stop []
(mount/stop))
(defn refresh []
(stop)
(tn/refresh))
(defn refresh-all []
(stop)
(tn/refresh-all))
(defn go
"starts all states defined by defstate"
[]
(start)
:ready)
(defn reset
"stops all states defined by defstate, reloads modified source files, and restarts the states"
[]
(stop)
(tn/refresh :after 'dev/go))
and dev/user.clj
(defn dev
[]
(require 'dev)
(in-ns 'dev))
it would be a nice feature to be able to print out the dependency graph that ends up being generated based on the namespaces
I'd like to suggest adding :resume
and :suspend
functions to defstate
. There is a library that does that for Component: https://github.com/weavejester/suspendable. From my experience, it is very convenient during development.
(mount/defstate foo
:start (println "!!! start")
:stop (println "!!! stop"))
(def app foo)
(mount/start)
Then app
throw an error mount.core.DerefableState cannot be cast to clojure.lang.IFn
. This is typical situation when you use ring
and have app
and routes
definition. This is how i get this situation.
It tooks me some time to discover it. If you can't fix it it will be nice to add information about it in the readme.
Hi,
We're using mount
to handle a single connection to a datomic database in our app, and
recently ran into an issue.
To fully describe what's happening here:
develop
and feature/add-feature
)develop
, db.clj
resides at src/datomic_to_catalyst/utils/db.clj
(ns: datomic-to-catalyst.utils.db
)Switching between the two branches and issueing any lein repl/run/ring command will cause re-compilation to be attempted, which triggers the error linked to above.
In order to work around the error, the following steps "work around" the issue:
<path to db.clj>
Repeating steps 0 .. 3 (on a different branch where db.clj is re-located) re-produces the error consistently.
Investigating further, my colleague found that disabling aot compilation also works-around this issue.
I found this which seems to indicate that this could be the cause of the issue here.
Specifically, the following passage was an "ah-ha!" moment:
set-logger! is a macro that makes use of the ns var. ns contains the current namespace and, due to what I’m assuming to be special semantics regarding this particular var, it shouldn’t be unquoted within a macro - which is why it was failing in AOT mode.
(Our project.clj is currently using aot compilation)
When I use start-with
or start-with-states
, I need to provide a new value/state that replaces the value specified by the :start
expression. I would like to have an ability to wrap/compose the original :start
expression.
For e.g., let us say we have a state holding a DB connection.
(defstate db-conn :start (init-db-conn))
I want to run my tests with a mock DB connection that simulates a slow connection. So, it will be very handy to have something like this:
(defstate mock-db-conn :start-with-compose (fn [orig-conn] (wrap orig-conn)))
(mount/start-with-states {#'db-conn #'mock-db-conn})
The :start-with-compose
key specifies a function that takes the original DB connection initialized in the state db-conn
as a parameter and produces a new connection that will be swapped in. So the initialization proceeds like this.
:start
expression, in this case (init-db-conn)
.:start-with-compose
and get the returned value.db-conn
state.Of course, this is a trivial example and one can very well argue that I can directly use (init-db-connection)
in my mock-db-conn
instead. But the ability to compose initialization expressions will be very handy when the initialization expressions are lot more complex.
when a namespace gets recompiled, in case it had states defined inside, it would stop and start (i.e. restart) these states to make sure they are not lost / bound to resources (more details).
this is usually a preferred behavior, but in some instances (see this discussion) it would be best to not restart states on recompile.
this needs more thinking so the original state references are not lost after "not restarting".
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."
Not really an issue; probably more a lack of understanding due to my newness to clojure.
I'm trying to understand your fun-with-value example.
lein test
on your code repo works fine, so clearly there isn't a bug.
However, when I try to use the approach in my code, I get errors like:
... .mount_test$g cannot be cast to java.util.concurrent.Future
I copied the test file as is into my project. I'm using boot, not lein, but otherwise it should work the same.
My stripped down version of your test is:
(ns calvad.watcher.mount-test
(:require [clojure.test :as t :refer [is are deftest testing use-fixtures]]
[mount.core :as mount :refer [defstate]]))
(alter-meta! *ns* assoc ::load false)
(defn g [a b]
(+ a b))
(defstate f-args :start g)
(defn start-states []
(mount/start #'calvad.watcher.mount-test/f-args))
(use-fixtures :once #((start-states) (%) (mount/stop)))
(deftest fun-with-values
(is (= (@f-args 41 1) 42)))
When run, I get
ERROR in (fun-with-values) (core.clj:2206)
expected: (= ((clojure.core/deref f-args) 41 1) 42)
actual: java.lang.ClassCastException: calvad.watcher.mount_test$g cannot be cast to java.util.concurrent.Future
Any hints on what is missing would be appreciated.
Hi,
One feature of Component is that multiple separate systems can be started in the same Clojure runtime with different settings. This is very useful for running tests against system.
As Mount attaches the state to namespaces, I presume that it's not possible to have multiple separate applications running in one Clojure runtime?
Would be great if you could mention this on differences to component documentation.
There are logging alternatives such as unilog or timbre, and mount should not bring tools.logging
with it: while it can be excluded, it's just an extra step that should be avoided.
The way mount currently logging states while bringing them up and down is quite helpful to resolve any exceptions during start/stop/suspend/resume, since it is visible which state caused it:
22:13:06.591 [nREPL-worker-0] INFO mount.core - >> starting.. app-config
22:13:06.592 [nREPL-worker-0] INFO mount.core - >> starting.. conn
22:13:06.767 [nREPL-worker-0] INFO mount.core - >> starting.. web-server
BindException Address already in use java.net.PlainSocketImpl.socketBind (PlainSocketImpl.java:-2)
so it is obvious that the answer to the problem is in starting.. web-server
.
On the other hand, mount adds details to the exception to make is clear when/where it occurred. For example if the above exception is looked at in REPL *e
, it would have the details about web-server
:
#error {
:cause "Address already in use"
:via
[{:type java.lang.RuntimeException
:message "could not start [web-server] due to" <<<<<<<<<<<<<<
:at [mount.core$up$fn__4959 invoke "core.clj" 55]}
{:type java.net.BindException
:message "Address already in use"
:at [java.net.PlainSocketImpl socketBind "PlainSocketImpl.java" -2]}]
:trace
[[java.net.PlainSocketImpl socketBind "PlainSocketImpl.java" -2]
[java.net.AbstractPlainSocketImpl bind "AbstractPlainSocketImpl.java" 382]
Therefore I believe it is safe to remove mount logging all together.
If logging is removed, and there are no exceptions during start/stop, it is not obvious which states have started / stopped / suspended / resumed.
Feels like all lifecycle functions need to simply either return all the states that they changed OR return the current app status:
({:name conn, :started? true, :suspended? false}
{:name nrepl, :started? true, :suspended? false}
{:name q-listener, :started? true}
{:name web-server, :started? false}
{:name test-conn, :started? false}
{:name test-nrepl, :started? false}
{:name app-config, :started? true, :suspended? false})
With simply returning a set of states a particular lifecycle method affected, there is a caveat: start
can also resume
.
I'd like to use this in a releasable uberjar, but the SNAPSHOT thing worries me.
Are there plans on the horizon for a 0.1.0 release?
In the Readme, under Start and Stop Parts of Application, it says that start
and stop
optionally take "namespaces". But they appear to take vars, no?
Can they also take namespaces, and will they then apply to every defstate
in the namespace? Or is that just a typo?
I frequently write command line programs that rely heavily on command line arguments for parameters. This is just one use case, but it would be great to somehow pass arbitrary data to the start functions like so:
(defn -main
[& args]
(mount/start {:config_file (first args)}))
Need a way to start an app with certain states being substituted. This is very useful for testing.
Currently I am thinking something like:
(mount/start-with {:db #'app/test-db
:sms-sender #'app/test-sms-sender)
The keys will most likely need to be prefixed with their ns
to avoid collisions.
These were added before realization that the same functionality can be achieved via:
(mount/stop-except ...)
and they unnecessary complicate the codebase making it harder to refacrtor / add new features.
I spoke to several people who use them in their apps, they agree that (mount/stop-except ...)
can be used in all the cases they've used suspend / resume.
Hence the plan is to remove this API (i.e. :suspend
/ :resume
)
The CLJS documentation mentions that (mount/in-cljc-mode)
has to be invoked "anywhere before a call to (mount/start), usually at the entry point of an app: in the -main, web handler, etc.".
This was not sufficient for me, as the namespaces that are required in the system
namespace containing the -main
method are evaluated before -main
is invoked. The solution was to add (mount/in-cljc-mode)
(right after after (ns ...)
) in each namespace that contains (defstate ...)
.
Maybe there is even a cleaner solution to the mode-switching.
Thanks for "mount"!
Would be niice to have open around
hooks into lifecycle methods. This would address "cross cutting concerns":
where all these functions can be custom made, and shared.
this could be a point of reference to bounce off of.
The cljs version of mount only allows states to be named as strings.
For example, on the example cljs app: https://github.com/tolitius/mount/blob/0825ad2ed085b73b7ae989b4382ce4e0376e4be3/dev/cljs/app/example.cljs
the app.audit-log/log
state is named as a string like this: (js/setTimeout #(mount/stop-except "#'app.audit-log/log") 500)
Since the cljs compiler never munges strings, having a lot of long namespaces will result in a lot of un-optimized references to mount states. It'd be awesome to find a way to optimize the mount state names as well during :advanced
compile!
In case of indirect dependencies sometimes it is needed to specify a particular order in which they start.
For example if we have a message bus and a subscriber that are indirectly connected via, say, a core.async channel, we need to make sure the subscriber does not start subscribing to the message bus before the bus is started.
It is not a simple task to validate the dependency order just for indirect dependencies:
i.e. in case a state
a
is "enriched" with a dependency on stateb
, how do we know that by startingb
a bit _earlier_, to satisfy this dependency, we not hurting it (b
) by now not _yet_ starting states thatb
naturally depends on
since mount records only a linear dependency / start order.
Therefore the idea (that belongs to @wkf) is to optionally specify all the dependencies on mount start e.g.:
(mount/start-with {:with-deps {... dependency graph / order ...}})
This has to do with start-with
rebranding #47, and, as a side effect, will add more dependency graph explicitness.
it's a small nitpick, but it's a bit more idiomatic to create a core namespace
At the moment there isn't a lot of guidance on how to deal with exceptions inside defstate
start/stop methods.
My case is mostly related to bad config - if a user provides bad arguments to a program (specifically a directory that is supposed to contain certain files, and if those files are missing parts of the program will not be able to function.) how can we deal with some state being bad?
@tolitius suggested (in slack) wrapping the start method in a (try ... (catch ... ))
which works if a default value can be supplied, however in many cases that may not be possible (database connection failure, for example)
In the database connection case currently I'm considering still using the try-catch and modifying application routes to prevent some actions, or changing having the defstate
hold false or nil value to indicate failure, and having reliant parts of the application check the value before doing work. This pushes that concern out across large parts of the system, especially since this is largely affects the views - we want to disable buttons and provide error hints, for instance.
$ boot repl
works fine with boot 2.5.2 (except for some deprecation warnings), but with boot version 2.6.0, I get:
clojure.lang.ExceptionInfo: template already refers to: #'boot.core/template in namespace: adzerk.boot-reload
data: {:file "adzerk/boot_reload.clj", :line 1}
java.lang.IllegalStateException: template already refers to: #'boot.core/template in namespace: adzerk.boot-reload
...
clojure.core/refer core.clj: 4098
...
clojure.core/apply core.clj: 632
clojure.core/load-lib core.clj: 5730
...
clojure.core/apply core.clj: 632
clojure.core/load-libs core.clj: 5749
...
clojure.core/apply core.clj: 632
clojure.core/require core.clj: 5832
...
adzerk.boot-reload/eval1054/loading--auto-- boot_reload.clj: 1
adzerk.boot-reload/eval1054 boot_reload.clj: 1
...
clojure.core/load/fn core.clj: 5866
clojure.core/load core.clj: 5865
...
clojure.core/load-one core.clj: 5671
clojure.core/load-lib/fn core.clj: 5711
clojure.core/load-lib core.clj: 5710
...
clojure.core/apply core.clj: 632
clojure.core/load-libs core.clj: 5749
...
clojure.core/apply core.clj: 632
clojure.core/require core.clj: 5832
...
boot.user/eval70 boot.user1854421973038905773.clj: 9
...
boot.main/-main/fn main.clj: 196
boot.main/-main main.clj: 196
...
boot.App.runBoot App.java: 399
boot.App.main App.java: 488
...
Boot.main Boot.java: 258
I see in Mount's examples that a state captures the output of the :start fn and that the :stop fn interacts with that result.
What if the arguments to the :start fn aren't known until runtime? For example, I'd like to pass in a runtime-generated set of routes to my web handler. So if I have the following function to launch a server:
(defn launch-server [routes port] (run-jetty routes {:port port}))
How do I define a state to capture the output of that function at runtime, that I can then use to close the server in my :stop fn?
(defstate http-server :start launch-server :stop ???)
Currently I'm rigging my own solution with an atom but it feels like I'm side-stepping mount rather than using it correctly:
(def server-atom (atom nil))
(defn launch-server [routes port] (reset! server-atom (run-jetty routes {:port port}))
(defstate http-server :start launch-server :stop (.stop @server-atom))
:suspend and :resume were removed but are still present in mount/dev/clj/app/utils/logging.clj
everything works in case the clojurescript
dependency is:
:dependencies [[org.clojure/clojurescript "version"]]
but when it is brought in as AOT compiled:
:dependencies [[org.clojure/clojurescript "version" :classifier "aot"]]
things get wild:
WARNING: Use of undeclared Var cljs.tools.reader/find-ns at line 368 dev/resources/public/js/compiled/out/cljs/tools/reader.cljs
WARNING: Use of undeclared Var cljs.tools.reader/ns-name at line 369 dev/resources/public/js/compiled/out/cljs/tools/reader.cljs
WARNING: Use of undeclared Var cljs.tools.reader/*ns* at line 386 dev/resources/public/js/compiled/out/cljs/tools/reader.cljs
WARNING: Use of undeclared Var cljs.tools.reader/tagged-literal at line 788 dev/resources/public/js/compiled/out/cljs/tools/reader.cljs
WARNING: Use of undeclared Var cljs.tagged-literals/uuid at line 31 dev/resources/public/js/compiled/out/cljs/tagged_literals.cljc
WARNING: Use of undeclared Var cljs.analyzer/munge at line 180 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/munge at line 337 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/*print-err-fn* at line 365 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/munge at line 406 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/ns-interns* at line 489 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/symbol-identical? at line 862 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/symbol-identical? at line 862 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/symbol-identical? at line 864 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/symbol-identical? at line 866 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/char? at line 986 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/char? at line 986 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/find-macros-ns at line 1675 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/find-macros-ns at line 1675 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/find-macros-ns at line 2303 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/find-macros-ns at line 2305 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/find-macros-ns at line 2307 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/find-ns at line 2308 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/find-macros-ns at line 2321 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/find-macros-ns at line 2323 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var cljs.analyzer/symbol-identical? at line 2349 dev/resources/public/js/compiled/out/cljs/analyzer.cljc
WARNING: Use of undeclared Var mount.tools.macro/defmacro at line 3 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/on-error at line 3 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/msg at line 3 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/f at line 3 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/f at line 5 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/msg at line 9 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/defmacro at line 11 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/throw-runtime at line 11 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/msg at line 11 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.tools.macro/msg at line 13 src/mount/tools/macro.cljc
WARNING: Use of undeclared Var mount.core/defmacro at line 130 /Users/tolitius/1/fun/mount/src/mount/core.cljc
WARNING: Use of undeclared Var mount.core/defstate at line 130 /Users/tolitius/1/fun/mount/src/mount/core.cljc
WARNING: Use of undeclared Var mount.core/state at line 130 /Users/tolitius/1/fun/mount/src/mount/core.cljc
WARNING: Use of undeclared Var mount.core/& at line 130 /Users/tolitius/1/fun/mount/src/mount/core.cljc
WARNING: Use of undeclared Var mount.core/body at line 130 /Users/tolitius/1/fun/mount/src/mount/core.cljc
WARNING: Use of undeclared Var mount.core/state at line 131 /Users/tolitius/1/fun/mount/src/mount/core.cljc
WARNING: Use of undeclared Var mount.core/body at line 131 /Users/tolitius/1/fun/mount/src/mount/core.cljc
WARNING: Use of undeclared Var mount.core/var? at line 185 /Users/tolitius/1/fun/mount/src/mount/core.cljc
WARNING: Use of undeclared Var datascript.core/uuid at line 232 dev/resources/public/js/compiled/out/datascript/core.cljc
need to learn more about the reasons.
would appreciate any help from cljs gurus.
While it's good to have separate APIs to swap / filter states needed. It is limiting if they don't compose.
We started discussing it here with @aroemers, and this was one of the primary reasons for a mount-lite spinoff.
I really want to preserve the vararg
ness of (mount/start)
/ (mount/stop)
since it is really simple and useful in REPL, hence I don't want to overload them. But I feel (mount/start-with)
can be a great candidate for composing the mount configuration. e.g.
(mount/start-with {:only #{}
:swap {}
:swap-states {}
:except #{}
:args {}
:with-deps {}})
This is the current approach that we discussed with @wkf on #mount
slack channel, and it does break the existing (mount/start-with)
, but I feel the timing and the nature of its use still permits it.
While this works fine:
(mount/start-with {#'app.sms/send-sms #'test.app/send-sms})
it requires #'test.app/send-sms
to be created, which could make it hard to narrow down the scope for the test (to a let binding).
Have start-with
to take instances instead
[(!) breaking, but worth it] / as an addition to
[might not worth it]:
(let [sms-ch (chan)
send-sms (fn [sms] (go (>! sms-ch sms)))]
(mount/start-with {#'app.sms/send-sms send-sms})
;; testing.. and
(mount/stop))
I was curious why the semantics of defstate had @ (deref) in the test file. Those semantics don't seem to work as described. lein test mount.test.fun-with-values fails as expected, but lein test passes (running complete test suite). There seems to be some test state that I'm not aware of. Is this a bug or a feature?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.