Fork me on GitHub
#mount
<
2018-05-24
>
conan14:05:55

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

conan14:05:13

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.

tolitius15:05:13

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}))

conan15:05:03

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

conan15:05:53

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

tolitius15:05:26

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

conan15:05:30

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

tolitius15:05:19

: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

conan15:05:24

this is really helpful though

conan15:05:55

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

(defn start
  "Httpkit's run-server returns a function which stops the server "
  [{{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))

conan15:05:34

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

tolitius15:05:12

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

conan15:05:40

sorry typo

tolitius15:05:05

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

conan15:05:55

oh nice, i wondered where those log lines came from

conan16:05:16

so this:

(defn start
  "Httpkit's run-server returns a function which stops the server "
  [{{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:
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
=>
{:started ["#'hanabi.config/config"
           "#'hanabi.db/db-conn"
           "#'hanabi.s3/s3-config"
           "#'hanabi.greenhouse/greenhouse"
           "#'hanabi.index/page"
           "#'hanabi.web/web-server"]}
(http/get "")
ConnectException Connection refused: connect  java.net.DualStackPlainSocketImpl.connect0 (DualStackPlainSocketImpl.java:-2)

conan16:05:45

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.

tolitius16:05:05

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:

=> (-> (http/get "") :body)
"hi there"

conan16:05:04

the mystery remains. thanks so much for your help!

conan17:05:18

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

conan17:05:47

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

conan17:05:59

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

conan17:05:29

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

conan17:05:35

so this works:

(defn start
  "Httpkit's run-server returns a function which stops the server "
  []
  (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 "
  [{{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))

conan17:05:02

config is a mount state defined in another namespace

conan17:05:22

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

conan17:05:35

it's some specs i have for these functions

conan17:05:08

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

conan17:05:08

so i have this:

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

conan17:05:45

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}))
=> #'user/web-server
(s/valid? (s/fspec :args (s/cat)) web-server)
=> true

conan17:05:07

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

conan17:05:36

:ret any? works as well

conan17:05:09

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

tolitius19:05:42

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.