Fork me on GitHub
#clojure
<
2023-02-15
>
Nazral14:02:53

Hi, is it ok to use the wrap-reload middleware from ring in production or is this frowned upon?

dpsutton14:02:40

That middleware takes a list of source directories to watch. Presumably in production you have a jar and thus no directories at all. In that case there are no directories to watch and it wouldn’t do very much

Nazral14:02:21

Ok thank you so it should be fine

dpsutton14:02:04

out of curiosity, why did you want to use it?

Nazral14:02:50

In compojure, we have a dev environment which uses wrap-reload for practicality and I was wondering if it presents any security issues once the project is uberjar'd

dpsutton14:02:21

no. it’s just watching a file system with nothing on it. preferable to strip it out as you compose your middleware. It also requires the dep to track namespaces looking for changes which will spin its wheels finding nothing. But ultimately not a security issue.

👍 2
Nazral14:02:25

I think I will still use an alias in deps.edn to remove it of the middleware list when not in dev environment

ghadi14:02:25

totally frowned upon in prod

ghadi15:02:34

I would go further and say that it's a smell in dev too: it suggests that the application is not repl friendly

Nazral15:02:04

I'm not sure I follow

ghadi15:02:42

if you redef a var in your repl, the new def should be visible

ghadi15:02:53

all the way through the http endpoint

ghadi15:02:52

there are common things that foil this, like closing over a value

ghadi15:02:58

or if you're working on middleware itself

Nazral15:02:00

isn't this precisely what wrap-reload does ?

Nazral15:02:11

making sure that all the redefs are available up to the http endpoint ?

ghadi15:02:57

IIRC, brutally by reloading a lot

ghadi15:02:11

(a lot = more than your change)

dpsutton15:02:13

also, if its really repl friendly you should just be able to eval a form and your system’s behavior changes. you only need wrap reload if this isn’t true. and then the only thing that can change is the version of your code that is saved as it reloads from disk rather than using the vars defined by you in the repl

Nazral15:02:38

fair enough

Ben Sless16:02:55

How would you work with middlewares and handlers which take functions as arguments? redefing them won't change the behavior because they were passed by value, unless you eta-expand them

seancorfield17:02:48

@UK0810AQ2 You can pass anonymous functions that wrap them (or even just use a Var via #' most times).

seancorfield17:02:10

I'm very opinionated about REPL-friendly development and these various reload libraries -- I discourage them whenever anyone mentions them (and I don't use them at all). I eval every top-level form as I edit it, including any ns form I change, and I use #' where necessary to support redefinition. Then I can start my app in the REPL and just leave it running while I work on it -- no restarts, no reloads, no breakage due to a "fancy" reload library breaking stuff (which does happen -- they're not a silver bullet). I was working on one of our web apps yesterday and had it running via the REPL nearly all day while I was adding features and debugging stuff (with tap> -- I ❤️ tap>!!!).

Ben Sless17:02:33

Passing anonymous functions which wrap them e.g. eta expansion is usually what I do, more compilation friendly when you use direct linking

seancorfield17:02:57

Yeah, we don't use direct linking in dev/test but when we AOT compile for staging/production JAR builds we enable direct linking -- and then we live with the restrictions that choice places on REPLs in staging/production (we run nREPL and Portal servers in a couple of our processes in staging/production).

dpsutton17:02:38

do either of you have ballpark perf improvements for direct linking that you saw?

seancorfield18:02:36

It AOT made a huge difference to app startup -- reducing some apps' startup from around one minute down to 10-20 seconds -- but we didn't bother to check general runtime. We only added AOT compile at all to get those startup times down, so our rolling deployments were faster/safer 🙂 [edited to clarify that it was primarily AOT that improved startup time]

dpsutton18:02:22

yeah we aot but haven’t looked into direct linking

dpsutton18:02:45

(apparently we did a while back and random things broke. will have to experiment later)

Ben Sless18:02:56

Impossible to truly know the effect of volatile reads a priori. Have to measure for each case

seancorfield18:02:18

I should restate my comment above: not all of that speed-up was due to direct linking, now that I think back to it. I'm not sure we actually measured independently of AOT but I think it made some difference to startup times. AOT made the "huge difference" but I'd need to trawl through JIRA to see when we added direct linking and what timings we did. I know we've gone back and forth on whether to keep direct linking (since it does make REPL-based debugging/patching harder).

dpsutton18:02:02

i made an issue and at the end was “make sure this tradeoff is worth it because repl’ing against a prod jar now is weird”

seancorfield18:02:45

Yeah, now we have our CI/CD pipeline times down for most changes, simply making a change and redeploying is fast enough that we aren't as concerned about trying to patch something "live" -- and in some ways it's good to discourage modification of production via the REPL 🙂 (although direct linking does also hinder debugging occasionally, due to not being able to simply redef a fn to add tap> or logging or whatever).

dpsutton19:02:58

we’d never repl to a live prod instance. but nice to grab the actual jar and run it locally

dpsutton19:02:13

vs same commit. nice to run the actual artifact

seancorfield19:02:35

Having nREPL/Portal in a couple of production apps has been great for debugging: start the VPN then hit a hotkey to a) start an SSH tunnel, b) start a browser in VS Code connected to the remote Portal server, and c) start an nREPL connection to the remote server. Then add-tap on the Portal submitter and start eval'ing code from a local RCF to debug stuff and view the data nicely. Then remove-tap when you're done and shut it all down. I blogged about it and shared a version of the Joyride script I used to automate it.

seancorfield19:02:46

https://corfield.org/blog/2022/12/18/calva-joyride-portal/ in case anyone wants to see how that particular sausage is made.

Joshua Suskalo20:02:00

In case you want a view of how you would handle reloadable http servers without wrap-reload or tools refresh or similar other utilities, I have an article which goes over reloadability which I feel could be useful to you: https://srasu.srht.site/var-evaluation.html

Joshua Suskalo20:02:52

(also @U04V70XH6 is it fine for me to just share something like that in a case like this were it feels perfectly applicable? It feels weird because it seems like self-promotion, but simultaneously it's exactly what I'd want to say on the subject and it says it better than I could pulling it out of a hat, and it's not like I'm making ad revenue off it)

seancorfield20:02:37

I just shared one of my blog posts in the thread so it seems eminently reasonable for you to share a relevant one of yours @U5NCUG8NR! 🙂

seancorfield20:02:46

I remember reading that when you wrote it last year and thinking "Oh, good article..." until I got to the gnarly macro at the end :rolling_on_the_floor_laughing: but you do warn people not to use it in general...

Ben Sless20:02:33

I just have this tiny macro lying around for such cases:

(defmacro $ [sym]
  `(fn
     ([x#] (~sym x#))
     ([x# y# z#] (~sym x# y# z#))))

Joshua Suskalo20:02:24

Yeah, I absolutely hate that macro I wrote, and I hate that I saw someone on hacker news say they were going to use it. I'm considering redacting it from the article entirely, but I genuinely do think it has some value in short-form teaching, and so I don't want to remove it.

Joshua Suskalo20:02:59

@UK0810AQ2 what kinds of code does that work for that #' wouldn't work for?

Ben Sless20:02:12

Doesn't introduce volatile read when you use direct linking. It should get inline by the JIT in production and give you a reloadable experience in dev time by way of (ab)using the compiler's value/reference semantics

Ben Sless20:02:30

that's only relevant in high throughput scenarios

Joshua Suskalo20:02:32

Ah, that makes some sense. I'd be curious what kind of impact it would make on performance.

Ben Sless20:02:44

I have a test case in mind

Ben Sless20:02:50

I might get to it this month

Ben Sless21:02:45

basically exercise a server with wrk with a trivial handler passed once by value and once as a var

seancorfield21:02:53

@U5NCUG8NR > I'm considering redacting it from the article entirely, but I genuinely do think it has some value in short-form teaching, and so I don't want to remove it. ( At one time I actively stopped putting example code in blog posts because folks would copy it without understanding it and then complain to me that "your code doesn't work" when they were using it in a context that just wasn't applicable. These days, I'm really careful to explain code with caveats but I still put a lot less examples in my blog posts than some people think I ought (#1 complaint is "you don't show (enough) code so I don't understand your post").

Joshua Suskalo21:02:34

That's actually part of why I deliberately made my example webserver use real libraries but refer to non-existing code, so that it was clear that I wasn't trying to build something that could be copy/pasted.

Joshua Suskalo21:02:03

The only downside is now people can't experiment with it in a repl without understanding what I was writing.

dpsutton21:02:14

@UK0810AQ2 have you written about that macro? I’d love to read the thinking and explanation behind it

Ben Sless21:02:30

I should write something, but the tldr: When you write a reitit handler, a specific route looks like ["path" {method {:handler foo}}] When you write it like that, foo is always passed by value If you pass a function which calls foo, the emitted instruction during indirect linking will include a var dereference which leaves foo dynamic wrt to redefinition The difference between passing foo as a var and this method is that during direct linking the emitted bytecode will be to the invokeStatic method in foo, and the method call in the surrounding function will probably be inlined, so there is zero performance overhead

Joshua Suskalo21:02:16

I need to look into how the inliner works more, because the max depth of 9 that it has by default and the fact that usually clojure function calls end up being two layers deep with invoke and invokeStatic makes me feel like that's an important thing for me to be able to think about when writing clojure code.

seancorfield23:02:35

> When you write a reitit handler And reitit doesn't allow Vars there, right?

Ben Sless04:02:48

It does, but I want to avoid var access while keeping the code dynamic

Ben Sless04:02:18

@U5NCUG8NR we should explore the impact of modifying MaxInlineLevel for Clojure applications

Nazral11:02:57

Just want to say thanks for all the resources, I've learned a lot!

Joshua Suskalo15:02:56

@UK0810AQ2 everything I've ever heard about optimizing things on the JVM says that you should never modify MaxInlineLevel because there's other optimizations and similar which rely on it being set to the default. I still think it'll be worth testing, but having heard all that is going to make me dubious of the performance benefit it will have, and honestly maybe even correctness.

Ben Sless15:02:26

I haven't seen those caveats, do you happen to have any reference? I'd be happy to read it. WRT correctness I wouldn't worry, the C2 compiler is good enough to not make those mistakes. In any case, the defaults are correct for the typical java application. Are they correct for the typical Clojure application? Without searching the configuration space, like in https://www.youtube.com/watch?v=RG9Ne2tkRuQ, we're guessing. There are several changes I think a Clojure application might benefit from, including bigger Eden area, bigger TLAB, more aggressive inlining, etc, but I don't touch those flags because I have no idea and haven't tested it yet. It's certainly something I'm interested in knowing, though, so in case anyone reading this wants to fund performance research in Clojure, hmu 😸

Joshua Suskalo15:02:28

That makes sense. Unfortunately I don't have a reference to where I saw that before. I've been trying to keep references to things more recently, but that's a new habit for me.

Ben Sless15:02:01

shoutout to the wonderful people at Logseq 🙂

Ben Sless15:02:04

An interesting point coming out of Thalinger's talks is that every application is a special snowflake wrt its performance profile and you should find the optimal configuration for it. Completely abandoning one-size-fits-all

ghadi16:02:02

start a new thread please

🙃 2
👍 4
marciol16:02:40

Yes, it def deserves a new thread, such interesting subject