tolitius / mount

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

How to reload (stop and start) a state on a -main function #72

Closed AlexAti closed 7 years ago

AlexAti commented 7 years ago

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?

tolitius commented 7 years ago

while true; do lein run; done is definitely a strange thing to do :)

Could you elaborate on what it is you are trying to accomplish?

Are you trying to work with this application inside the REPL or you are trying to run it as a java -jar app.jar?

I am also not sure what you are trying to accomplish with start-with-states in the -main function?

AlexAti commented 7 years ago

Well what I'm trying to do is to have a -main function that both serves for my uberjar, and be able to reload the server (that runs locally through -main with "lein run") while developing. My problem is that now, whenever I change a file, mount tries to reload http-kit by stopping it and starting it, but this is what happens:

">> starting.. #'myserv/server-config (namespace was recompiled)"
"<< stopping.. #'myserv/myserv-server (namespace was recompiled)"
">> starting.. #'myserv/myserv-server (namespace was recompiled)"
Alexs-MacBook:myserv alex$ 

So, the process ends and I'm kicked out to the shell... while my expectation was that the server would be restarted and kept running. I understand that perhaps my -main should have something more, but in this example I see just a start function: https://github.com/tolitius/mount/blob/master/doc/uberjar.md#uberjar-is-the-main I guess that perhaps in that example the uberjar also doesn't reload? And the problem is more in my workflow (using lein run insted of lein repl and manual start?), as reloading is more for development. But wouldn't that happen also in the lein repl??

The start-with-states function is due to another problem that I have with a defstate with a korma connection.

tolitius commented 7 years ago

I am still unclear why you need start-with-states as it usually used for testing.

But I think the confusion comes from the runtime args. Usually I use cprop to deal with all kinds of properties and arguments. Here is an example app with http-kit that uses cprop.

Here is your code a bit altered to work:

(ns your.app
  (:require [org.httpkit.server :as hk]
            [mount.core :as mount :refer [defstate]]))

(defn args->sever-config [args]
  {:port (Integer/parseInt
           (or (first args)
               (System/getenv "PORT")
               "7890"))
   :join? false})

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hi from http kit!"})

(defn start-server [server-config]
  (when-let [server (hk/run-server app server-config)]
    (println "Server has started!")
    server))

(defstate my-server :start (start-server (args->sever-config
                                           (mount/args)))
                    :stop  (my-server :timeout 100))

(defn parse-args [args]
  ;; parse args here to return what's needed
  args)

(defn -main [& args]
  (-> args
      parse-args
      mount/start-with-args))

Here is how you can run / reload app at REPL time:

$ lein do clean, repl

user=> (require '[your.app]
                '[mount.core :as mount])
nil
user=> (mount/start)
Server has started!
{:started ["#'httpkit.core/my-server"]}

at this point you can access http://localhost:7890/ and see hi from http kit!.

You can stop it / restart it, etc..:

user=> (mount/stop)
{:stopped ["#'httpkit.core/my-server"]}
user=>

user=> (mount/start)
Server has started!
{:started ["#'httpkit.core/my-server"]}
user=>

user=> (mount/stop)
{:stopped ["#'httpkit.core/my-server"]}

Since this is started with start-with-args, the app can also be passed runtime params. More about it here.

But again params / configs / properties are better handled by cprop.

AlexAti commented 7 years ago

Ok, thanks a lot! So I had a couple of misconceptions.

  1. I saw a problem that I had with environ, so you convinced my to use cprops to not reinvent the wheel :)

  2. I guess that with your library you kill (or make obsolete) my old workflow of having "lein run" use ring's wrap-reload to reload changed filenames. This is something I can live with, as on the other hand I gain the possiblity of redefining routes, have the server recompile (I think this didn't work for me beforehand), restart the app when I change databases, etc.. So now I use "lein repl", start the server with mount, and everything works!

  3. I see you use start-with-args instead of start-with-states (which I didn't know it was mainly intended for debugging) to pick -main 's args into a state. So now I use it too :)

Thanks for mount!

tolitius commented 7 years ago

great, thanks for the feedback!