This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-01-14
Channels
- # announcements (1)
- # babashka (19)
- # beginners (50)
- # biff (7)
- # calva (6)
- # cider (13)
- # circleci (7)
- # clj-kondo (49)
- # clojure (45)
- # clojure-belgium (1)
- # clojure-europe (2)
- # clojure-indonesia (1)
- # clr (12)
- # datomic (3)
- # events (7)
- # fulcro (4)
- # graphql (2)
- # gratitude (1)
- # instaparse (5)
- # lsp (17)
- # off-topic (26)
- # polylith (15)
- # portal (4)
- # remote-jobs (2)
- # spacemacs (12)
I'm trying to figure out how dynamic vars work internally but having hard time to understand the Java impl. From what I can infer there is a tree (or a stack?) of thread-local environments (basically maps var->value) built via binding
and the dynamic vars will traverse this tree to find their root upon deref. The thing I don't understand is how do we know which thread-local dynamic environment we are currently in while executing deref? How do we avoid dynamic lookups for non-dynamic vars? Could somebody eli5 the idea behind dynamic var implementation to me?
The compiler looks at the var metadata when generating bytecode and emits a call to getRawRoot (or a method that is named something similar) unless the var is marked as dynamic
originally all vars were dynamic, but the perf hit was not worth it when dynamic was not needed
so the default was switched to non-dynamic (overridden with ^:dynamic)
I went the other way and implemented only non-dynamic vars first, now I'm trying to implement binding
and it gives me a headache 😄
I'm not sure if the question on "how do we know which thread-local dynamic environment we are currently in while executing deref" was answered, but it's using a ThreadLocal for the current frame stack
so it's inherently thread-specific
there are some context where the binding environment is migrated to a new thread (like with futures, agents, etc)
I see, and since dvals
is static it is shared across all vars, each thread having its own chain of Frame
s there
so upon accessing a dynamic var we essentially perform lookup across bindings in those Frame
s and ThreadLocal
makes sure we walk the right chain?
yes, top frame in current thread and then on down
It doesn’t need to walk down the frames, just grab the current frame, which contains a map with all bindings
ah, but pushThreadBindings
seems to make a new frame with only the bindings passed to it
we could have something like:
(def ^:dynamic *x* 1)
(def ^:dynamic *y* 9)
(binding [*x* 2]
(binding [*x* 3]
(println *x* *y*))
(println *x* *y*))
(println *x* *y*)
If I understand this correctly, *y*
is only bound at the top here so whenever we use *y*
here the lookup will go back to the top scope to find itIt gives the same result as a different implementation that walks the bindings in the stack, but takes advantage of persistent hash maps to optimize that.
sorry for my misdirect
all good, I'm asking all those questions because I want to implement this myself and ultimately my impl will be different in many details like that. Mainly because I have my own VM that is totally different from JVM 😅
@UJEK1RGJ0 if you environment is single threaded, the impl can be much simpler. e.g. CLJS just uses mutation in place for dynamic vars
I typically reach for ring + jetty when developing web servers, but I'm struggling to find a definitive standard "here's how to add a web socket host" that doesn't involve switching to a different web server or bring in something like core.async. is my google-fu simply failing?
sunng's jetty9 adapter includes websocket support (and actually uses Jetty 11, despite the 9).
We switched from the default ring/jetty adapter to ring/jetty9 (i.e., 11) at work recently so we could have simple WS support and it works beautifully -- and everything else in our stack is still happy because it's still jetty, including all our monitoring etc.
I blogged about it a bit in https://corfield.org/blog/2022/12/07/deps-edn-monorepo-10/
There's also this Jetty wrapper, also supports WS https://github.com/factorhouse/slipway
this is great, thanks @U04V70XH6!
I also looked at slipway but was very confused about what it even does. It wasn't obvious to me until after much reading that it's a sort of replacement for ring + the ring jetty adapter
fwiw, if you search for "jetty websocket" on https://phronmophobic.github.io/dewey/search.html, the https://github.com/sunng87/ring-jetty9-adapter adaptor is the first result.
Also, regardless of WebSocket support, Jetty 9 is well out of community support at this point and several CVEs have been reported against it lately so updating to Jetty 11 makes sense (although Jetty 9 is still getting critical security fixes I believe) -- and the default ring adapter is not likely to get updated to Jetty 10/11 per https://github.com/ring-clojure/ring/issues/439
I was unsure what the tradeoff was using the (newer, less battle tested from the outside) jetty9 adapter vs the official one. sounds like the official one ought to be deprecated in favor of the the jetty9+ one
i'd also recommend sunng's jetty9+ adapter and have been using it happily for years. in case it's helpful here's an example of building a registry of connections and adapters for core.async on top of the jetty9 websockets: https://github.com/RutledgePaulV/websocket-layer
Aleph provides a web-server which is compatible with Ring handlers and middleware with WS support as well.
One thing to consider for production usage is observability: you need to endure whatever you select is well-supported by your existing monitoring services and tools. We went from Jetty to http-kit but New Relic doesn't support it so we went back to Jetty. We also have a Netty-based service and New Relic struggles to give useful information with that. Aleph is Netty-based so that would be a concern for me.