Fork me on GitHub
#juxt
<
2021-02-23
>
malcolmsparks08:02:25

No, but afaik it's something you can do yourself. @dominicm might be able to help more

dharrigan08:02:08

It may be achievable by using profiles perhaps

flowthing12:02:53

Yeah, good point, it might be possible to reorganize my Aero config to achieve what I'm looking for. I have a feeling that might be a bit clunky, but maybe I'll give it a go.

flowthing12:02:53

Also, this might be more of a general Clojure question than a Clip-specific question, but: Given a Ring handler like this:

(def app (wrap-middleware my-handler))
To have your changes take effect without having to restart the HTTP server, you can pass the handler var to http-kit like this:
(org.httpkit.server/run-server #'app options)
Now, I can just re-evaluate (def app ,,,) and my changes take effect. However, with Clip, I have something like this in my Aero config:
:handler {:start (app.server.handler/app {:db (clip/ref :db)})}
:http {:start (org.httpkit.server/run-server (clip/ref :handler) {:port 1337})
       :stop (this)}
Now, re-evaluating app.server.handler/app is no longer enough to make my changes live. I have to do a whole-app reset with clojure.tools.namespace.repl. I haven't figured out a way to arrange things such that I can just re-evaluate my handler and have my changes take effect immediately. Any thoughts?

dominicm16:02:30

This is something I want to investigate further, but it's a pitfall shared with integrant and component. I will add that the performance benchmarks I've done indicate there's no significant difference between using the handler directly, and "creating" the handler on every request.

flowthing18:02:57

Interesting, thanks! With the amount of traffic my apps get, that might indeed be an option. I also tried hacking together a solution with alter-var-root, but I couldn't make it work. I haven't used Component, but Integrant does indeed suffer from the same problem.

flowthing18:02:07

Maybe there's a way to use profiles to recreate the handler function on every request in dev but not in prod.

dominicm10:03:34

You could do this:

(defn app [& args]
  (if (:direct-linking *compiler-options*)
    (wrap-middleware (apply my-handler args))
    (fn [req]
      ((wrap-middleware (apply my-handler args)) req))))
And something could potentially automate that for you too

dominicm10:03:31

Or maybe even (defn app [{:keys [re-eval?] :as opts}] (if re-eval? (fn …) (wrap-middleware …)))

flowthing17:03:15

Yeah, that's probably the way to go. I'm always a bit wary of code that works differently in dev and prod, though. 🙂

dominicm07:03:08

I just hacked together a proof of concept where clip can reconstruct your function every time it's called. It's a little bit funky (it calls your code, does a fn? check, then decides to rewrap it in that case). This might be a good use-case for custom lifecycles (as that's how I hacked it in, it runs after :start and detects the fn and replaces it in that case. It might also be a good use-case for some kind of explicit transformation system.

3
dominicm15:04:41

If you'd like to test eval-only reloadables out, you can use sha b0ec15949e54c62d8c9be406f05d24dc62a679b0 and add this to your system:

:reloads {yada.resource.Resource juxt.clip.repl.yada/reloadable-resource
             clojure.lang.Fn juxt.clip.repl/relodable-fn}
Note that no resolution happens on those values yet, so you'll need to make sure they're not coming from edn as symbols (e.g. by just assoc it on in your code portion). The yada key is only for yada users :) I'm quite unhappy with the edn interpreter atm, so I will likely focus on re-writing that before releasing this.

dominicm21:04:56

New sha to use for above ^ b288f368410156489e4b0e5223abcc528195aa98 has new impl I'm more confident in & uses new evaluator.

3
flowthing16:04:38

Looks very cool! I'll definitely give it a try next week at work.

flowthing09:04:58

Noticed a typo: relodable-fn. Other than that, the reloading part seems to work great. :thumbsup::skin-tone-2: I think this is a hugely useful feature. However, calling juxt.clip.repl/stop fails with "Unable to evaluate form (#'org.httpkit.server/run-server #object[juxt.clip.repl$relodable_fn$reloadable_wrapper__1914 0x33590414 \"juxt.clip.repl$relodable_fn$reloadable_wrapper__1914@33590414\"] {:port 7070})" with that SHA. I'm probably doing something wrong. I don't quite understand what you mean by this: > you'll need to make sure they're not coming from edn as symbols (e.g. by just assoc it on in your code portion). All I did is updated deps.edn to use that SHA and added this into my Aero config: :reloads #profile {:dev {clojure.lang.Fn juxt.clip.repl/relodable-fn}}

dominicm09:04:37

That error sounds familiar, but I don't remember why 🙂

dominicm09:04:17

Very odd that it seems to be running your start function on stop

dominicm09:04:41

> you'll need to make sure they're not coming from edn as symbols (e.g. by just assoc it on in your code portion). ^ this isn't relevant with the later sha I think 🙂

flowthing09:04:18

Yeah, I'll try to find the time to dive into the problem next week.

dominicm10:04:11

Ah, I see the problem

dominicm10:04:20

http-kit returns a function, so clip is trying to reload it for you :)

dominicm10:04:12

@flowthing This solved it:

(def other-system-config
  {:components
   {:handler `{:start (handler {})}
    :http '{:start (org.httpkit.server/run-server (clip/ref :handler) {:port 8080})
            :stop (this)}}
   :reloads
   `{clojure.lang.Fn repl/relodable-fn
     :http nil}})

flowthing10:04:10

Ah, of course! Thanks, I’ll try that out.

dominicm10:04:15

When this hits master, API might actually be more like:

(def other-system-config
  {:components
   {:handler `{:start (handler {})}
    :http '{:start (org.httpkit.server/run-server (clip/ref :handler) {:port 8080})
            :stop (this)
            :reload nil}}
   :reloads
   `{clojure.lang.Fn repl/relodable-fn}})
As I think it's important to localize how the reload works. On the fence about whether matchers should be predicate based or some such.

dominicm10:04:17

Could definitely see some kind of "convention" where a team does:

(def other-system-config
  {:components
   {:handler/a `{:start (handler {})}
    :handler/b `{:start (handler {})}
    :router '{:start [["/about" (clip/ref :handler/a)]
                      ["/foobar" (clip/ref :handler/b)]]}
    :http '{:start (org.httpkit.server/run-server (clip/ref :handler) {:port 8080})
            :stop (this)
            :reload nil}}
   :reloads
   {(fn [k _v] (= "handler" (namespace k))) repl/relodable-fn}})

dominicm10:04:52

General solution is good, but API is still open for debate.

dominicm10:04:19

Could also add some data to the exception when this happens too, and fall back onto the original. Not sure if stop should operate on the original item or the wrapped one either…

flowthing13:01:08

Wondering what became of this in the end...? The API discussed in this thread no longer seems to exist, at least. 🙂

dominicm21:04:18

I still want to build this. I'll probably give it some love soon.

👍 1
Michaël Salihi12:02:43

@flowthing Your configuration looks like good for me. You can check my following repo for which eloaded works: https://github.com/PrestanceDesign/todo-backend-clojure-reitit/tree/clip Maybe it can help.

flowthing13:02:04

As far as I can tell, in that example, to have changes to your handler or router take effect, you have to call (user/reset), which is what I want to avoid.

Michaël Salihi13:02:03

OK I will better read your request. 👍

dominicm15:04:41
replied to a thread:Also, this might be more of a general Clojure question than a Clip-specific question, but: Given a Ring handler like this: (def app (wrap-middleware my-handler)) To have your changes take effect without having to restart the HTTP server, you can pass the handler var to http-kit like this: (org.httpkit.server/run-server #'app options) Now, I can just re-evaluate `(def app ,,,)` and my changes take effect. However, with Clip, I have something like this in my Aero config: :handler {:start (app.server.handler/app {:db (clip/ref :db)})} :http {:start (org.httpkit.server/run-server (clip/ref :handler) {:port 1337}) :stop (this)} Now, re-evaluating `app.server.handler/app` is no longer enough to make my changes live. I have to do a whole-app reset with clojure.tools.namespace.repl. I haven't figured out a way to arrange things such that I can just re-evaluate my handler and have my changes take effect immediately. Any thoughts?

If you'd like to test eval-only reloadables out, you can use sha b0ec15949e54c62d8c9be406f05d24dc62a679b0 and add this to your system:

:reloads {yada.resource.Resource juxt.clip.repl.yada/reloadable-resource
             clojure.lang.Fn juxt.clip.repl/relodable-fn}
Note that no resolution happens on those values yet, so you'll need to make sure they're not coming from edn as symbols (e.g. by just assoc it on in your code portion). The yada key is only for yada users :) I'm quite unhappy with the edn interpreter atm, so I will likely focus on re-writing that before releasing this.