mount

conan 2018-05-24T14:22:55.000660Z

Hi! I'm having real trouble persuading a web-server component to start. I'm using httpkit, which has a run-server function. When I run mount/start, mount starts the web server component, but it doesn't seem to actually start the web server. I must be doing something wrong with the :start function but I can't figure out what. Very grateful for any help. I've got a sample project that demonstrates the problem here: https://github.com/conan/mount-test/blob/master/src/mount_test/core.clj

conan 2018-05-24T14:24:13.000689Z

on startup I get {:started ["#'mount-test.core/web-server"]} shown, and another call to mount/start doesn't start anything, so mount has definitely done its job.

tolitius 2018-05-24T15:24:13.000654Z

http-kit's run-server function takes a map of options. you need to pass port as a value in this map:

(defn start
  [port]
  (println "Starting on port " port)
  (server/run-server (app)
                     {:port port}))

conan 2018-05-24T15:38:03.000536Z

oh yes, that is true. it's correct in my real application, but it does at least show that my sample project is not a reproduction of the issue

conan 2018-05-24T15:45:53.000695Z

is there some difference between including the code for :start inside the defstate macro and putting it in a separate function?

tolitius 2018-05-24T15:47:26.000689Z

I can start the server that does listen on the port you provided once I add {:port port}. there is no difference between including the code and calling a function name with the code

conan 2018-05-24T15:47:30.000099Z

so in the above example, doing :start (server/run-server (app) {:port 4321}) as opposed to calling the function

tolitius 2018-05-24T15:48:19.000071Z

:start (server/run-server (app) {:port 4321}) yep, that's fine. I usually just call a function from :start / :stop since it is easier to work with this function from the REPL

conan 2018-05-24T15:48:24.000050Z

this is really helpful though

conan 2018-05-24T15:49:55.000955Z

so here's my real app. this doesn't work:

(defn start
  "Httpkit's run-server returns a function which stops the server <http://http-kit.org/migration.html>"
  [{{port :config.web/port :as web-config} :config/web}]
  (when-let [config-error (s/explain-data :config/web web-config)]
    (throw (ex-info "Failed to start web server, invalid config" config-error)))
  (log/info "Starting web server on port:" port)
  (server/run-server (app) {:port port}))

(defstate web-server
  :start (start config)
  :stop (web-server))
but, i've just discovered thanks to your help, this does:
(defstate web-server
  :start (let [{{port :config.web/port :as web-config} :config/web} config]
           (when-let [config-error (s/explain-data :config/web web-config)]
             (throw (ex-info "Failed to start web server, invalid config" config-error)))
           (log/info "Starting web server on port:" port)
           (server/run-server (app) {:port port}))
  :stop (web-server))

conan 2018-05-24T15:51:34.000685Z

it should all be the same, but calling out to a separate function means the run-server never gets run. i assume i've done something really silly, but i can't spot it

tolitius 2018-05-24T15:53:12.000757Z

you are calling (start 8888), but the function takes a map of params: [{{port :config.web/port :as web-config} :config/web}]

conan 2018-05-24T15:57:40.000551Z

sorry typo

tolitius 2018-05-24T15:58:05.000904Z

also I see that you doing prn manually to log when states start / stop. take a look at mount-up: https://github.com/tolitius/mount-up it'll do logging for you

conan 2018-05-24T15:58:55.000486Z

oh nice, i wondered where those log lines came from

conan 2018-05-24T16:03:16.000641Z

so this:

(defn start
  "Httpkit's run-server returns a function which stops the server <http://http-kit.org/migration.html>"
  [{{port :config.web/port :as web-config} :config/web}]
  (when-let [config-error (s/explain-data :config/web web-config)]
    (throw (ex-info "Failed to start web server, invalid config" config-error)))
  (log/info "Starting web server on port:" port)
  (server/run-server (app) {:port port}))

(s/fdef start
  :args (s/cat :config :ef/config)
  :ret (s/fspec :args (s/cat)))

(defstate web-server
  :start (start config)
  :stop (web-server))
yields this when I run mount/start:
18-05-24 16:02:15 EF-XPS INFO [hanabi.config:16] - Loading config
18-05-24 16:02:15 EF-XPS INFO [hanabi.db:43] - Creating database at: datomic:<dev://localhost:4334/hanabi>
18-05-24 16:02:17 EF-XPS INFO [hanabi.db:34] - Migrating database
18-05-24 16:02:17 EF-XPS INFO [hanabi.s3:21] - Using s3 in region: eu-west-2
18-05-24 16:02:17 EF-XPS INFO [hanabi.greenhouse:250] - Starting job-posts schedule
18-05-24 16:02:17 EF-XPS INFO [hanabi.index:25] - Pre-caching index.html
18-05-24 16:02:17 EF-XPS INFO [hanabi.web:213] - Starting web server on port: 8888
=&gt;
{:started ["#'hanabi.config/config"
           "#'hanabi.db/db-conn"
           "#'hanabi.s3/s3-config"
           "#'hanabi.greenhouse/greenhouse"
           "#'hanabi.index/page"
           "#'hanabi.web/web-server"]}
(http/get "<http://localhost:8888/favicon.png>")
ConnectException Connection refused: connect  java.net.DualStackPlainSocketImpl.connect0 (DualStackPlainSocketImpl.java:-2)

conan 2018-05-24T16:04:45.000153Z

The log line you spotted runs, but the web server does not start. It's clearly a problem like the ones you've been (very kindly) pointing out, because mount has found and started the component.

tolitius 2018-05-24T16:20:05.000248Z

might have to do with your config's, since if you fix port (i.e. {:port port}) in your sample app, the server starts fine, and routes work. this is directly from your sample app:

=&gt; (-&gt; (http/get "<http://localhost:4321/>") :body)
"hi there"

conan 2018-05-24T16:50:04.000599Z

the mystery remains. thanks so much for your help!

conan 2018-05-24T17:10:18.000008Z

ok, the problem may stem from referring to another mount component (`config`) inside the defstate macro

conan 2018-05-24T17:10:47.000098Z

or at least, calling out to my start function works if i do not pass the config to it

conan 2018-05-24T17:10:59.000688Z

(i.e. it refers to config directly as a side cause)

conan 2018-05-24T17:11:29.000690Z

maybe there's some kind of interaction when referring to other mount components inside defstate

conan 2018-05-24T17:13:35.000280Z

so this works:

(defn start
  "Httpkit's run-server returns a function which stops the server <http://http-kit.org/migration.html>"
  []
  (let [{{port :config.web/port :as web-config} :config/web} config]
    (when-let [config-error (s/explain-data :config/web web-config)]
    (throw (ex-info "Failed to start web server, invalid config" config-error)))
    (log/info "Starting web server on port:" port)
    (server/run-server (app) {:port port})))

(defstate web-server
  :start (start)
  :stop (web-server))
but this does not:
(defn start
  "Httpkit's run-server returns a function which stops the server <http://http-kit.org/migration.html>"
  [{{port :config.web/port :as web-config} :config/web}]
  (when-let [config-error (s/explain-data :config/web web-config)]
    (throw (ex-info "Failed to start web server, invalid config" config-error)))
  (log/info "Starting web server on port:" port)
  (server/run-server (app) {:port port}))

(defstate web-server
  :start (start config)
  :stop (web-server))

conan 2018-05-24T17:14:02.000379Z

config is a mount state defined in another namespace

conan 2018-05-24T17:17:22.000015Z

correction: the problem is not caused by referring to the config

conan 2018-05-24T17:17:35.000044Z

it's some specs i have for these functions

conan 2018-05-24T17:19:08.000556Z

if i have a :ret spec on the :start function then it doesn't work

conan 2018-05-24T17:19:17.000168Z

🤯

conan 2018-05-24T17:21:08.000684Z

so i have this:

(s/fdef start
  :args (s/cat :config :ef/config)
  :ret (s/fspec :args (s/cat)))

conan 2018-05-24T17:21:45.000151Z

if i comment out the :ret spec in there then it works. i'm fairly sure the spec is correct because of this:

(def web-server (server/run-server (web/app) {:port 8889}))
=&gt; #'user/web-server
(s/valid? (s/fspec :args (s/cat)) web-server)
=&gt; true

conan 2018-05-24T17:22:07.000020Z

i'm using expound and orchestra, maybe they are interacting with defstate somehow

conan 2018-05-24T17:24:36.000006Z

:ret any? works as well

conan 2018-05-24T17:30:09.000612Z

I've updated the sample project with a spec and now i can reproduce

tolitius 2018-05-24T19:41:42.000346Z

I only use a little of spec and just for validation so I am not sure how spec affects this function. mount does not do anything to a :start / :stop references. it just calls them whenever (mount/start) or (mount/stop) are called.