This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-05-07
Channels
- # announcements (16)
- # asami (15)
- # babashka (12)
- # beginners (38)
- # calva (32)
- # cider (1)
- # clj-commons (9)
- # clj-otel (4)
- # clojure (57)
- # clojure-europe (43)
- # clojure-korea (1)
- # clojure-nl (1)
- # clojure-norway (13)
- # clojure-uk (4)
- # clojuredesign-podcast (9)
- # clojurescript (10)
- # cursive (5)
- # datahike (9)
- # deps-new (2)
- # events (1)
- # fulcro (8)
- # hyperfiddle (7)
- # kaocha (1)
- # lsp (2)
- # malli (3)
- # nrepl (2)
- # off-topic (19)
- # releases (3)
- # ring (10)
- # shadow-cljs (4)
- # sql (14)
- # xtdb (57)
- # yamlscript (2)
Is there anything that watches clojure files and compiles them in order when one or 2 get updated? I don’t think https://github.com/tonsky/clj-reload or clojure.tools.namespace.repl have the watch functionality
Perhaps you could pair clj-reload with a file watcher such as https://github.com/nextjournal/beholder?
I’ve used beholder with clj-reload and it works well. But my current setup is custom calva’s custom repl command to do the reload. Mostly because of my obsessive cmd+s habit on every new line of code,
Thanks, @U3X7174KS and @U06G9HMLGPM! I wanted to make sure I wasn’t reinventing the wheel here 🙂
Any time! Happy to aid in both wheel-finding and wheel-inventing, whatever floats your boat.
Good point! I'll have to invent some better metaphors. "whatever gets your engine humming"?
I think your metaphor was perfect. I am a fan of mixing them up, gives some spice to life. I was just doing the "annoying person" role-play.

Is there a good resource on designing DSLs and identifying domain language of your business? With Clojure/Lisp/ Or any other language?
I don't have anything specific but this sounds like the Racket community's kind of jam
love his writing!
Domain Driven Design has also ideas about this of course. You have to translate it a bit into the functional world but in my opinion, ubiquitous language and model driven also applies to clojure apps, at least at medium scale.
How different is creating these DSLs with Racket vs say using instaparse with Clojure?
big picture, both can do the thing but Racket, being permeated with this as their raison d'etre, has things like #lang
declarations to streamline the process. other than that the thing that jumps out to me is that instaparse is quite slow.
one thing to think about is, do you need a DSL with custom parsing or does a bit of EDN data do the job. This is what I went for with https://github.com/soulspace-org/overarch and I've written a bit about the decision in the rationale and the design document..
core.async question
I am playing with / using https://github.com/brunoV/throttler to learn more about how to use core.async
It's a library for rate limiting that uses a token bucket algorithm. The current implementation is that if there is no token available, the function call processing is blocked until a token becomes available.
I am trying to understand how does alts!
work. My idea for modifying the library is that if a token is not available we should default to a throttled path instead of blocking and waiting. But I don't know and I am trying to understand what it does to my token bucket channel.
original code:
(defn- chan-throttler* [rate-ms bucket-size]
(let [sleep-time (round (max (/ rate-ms) min-sleep-time))
token-value (round (* sleep-time rate-ms)) ; how many messages to pipe per token
bucket (chan (dropping-buffer bucket-size))] ; we model the bucket with a buffered channel
;; The bucket filler thread. Puts a token in the bucket every
;; sleep-time seconds. If the bucket is full the token is dropped
;; since the bucket channel uses a dropping buffer.
(go
(while (>! bucket :token)
(<! (timeout (int sleep-time)))))
;; The piping thread. Takes a token from the bucket (blocking until
;; one is ready if the bucket is empty), and forwards token-value
;; messages from the source channel to the output channel.
;; For high frequencies, we leave sleep-time fixed to
;; min-sleep-time, and we increase token-value, the number of
;; messages to pipe per token. For low frequencies, the token-value
;; is 1 and we adjust sleep-time to obtain the desired rate.
(fn [c]
(let [c' (chan)] ; the throttled chan
(go
(while (<! bucket) ; block for a token
(dotimes [_ token-value]
(when-not (pipe c c')
(close! bucket)))))
c'))))
my modification:
(fn [c]
(let [c' (chan)] ; the throttled chan
(go
;; (while (<! bucket) ; block for a token
(while true
(let [[v _] (alts! [bucket] :default false)]
(if v
(dotimes [_ token-value]
(when-not (pipe c c')
(close! bucket)))
(>! c' :rejected)))))
c')))
I wonder if the <!
executed by alts! will eventually execute and eat my token from the token bucket?I think, instead of alts!
you can use poll!: https://clojuredocs.org/clojure.core.async/poll!
But AFAIK, neither poll!
, nor alts!
can retroactively consume the values from the channel if the control flow already went forwarrd.
thank you!!!
Note that your modified code will spin endlessly and without blocking, so this is probably not what you have in mind.
ehh... true, needs some more thought
but the while loop will keep going
but maybe it's ok
I think you will still need alts!
to differentiate between no value and closed channel.
Undertow/websocket-related question:
Has anyone noticed bugs with PerMessageDeflateHandshake
? I am using PerMessageDeflateHandshake, and am sending cursor positions over websockets.
When I open two tabs and start moving the cursor. After a while I get an incomplete websocket message. it usually looks like this:
e.data
// => -space-default--main-123":{"x":505,"y":254,"xPercent":39.4550daeomm-ec86e8":"-bc6ini0997feab77}}}
// It looks like the beginning of the map didn't make it
Or sometimes, even like this:
{"op":"refresh-presence","room-id":"123","data":{"6b23c7c2-bb1f-4d14-a2c0-ece1a7549a51":{"peer-id":"6b23c7c2-bb1f-4d14-a2c0-ece1a7549a51","user":null,"data":{}},"5689634d-16eb-4eea-8fc2-29550c528456":{"peer-id":"5689634d-16eb-4eea-8fc2-29550c528456","user":null,"data":{"cursors-space-default--main-123":{"x":376,"y":428,"xPercent":29.375,"yPercent":65.0455927f3c5718}}},"44d83fb9-82c5-4d4d-82b8-3abb5754de2f":{"peer-id":"44d83fb9-82c5-4d4d-82b8-3abb5754de2f","user":null,"data":{}}}}
Here, yPercent
looks really weird: 65.0455927f3c5718
Here's where I added PerMessageDeflateHandshake:
(defn ws-request [^HttpServerExchange exchange ^IPersistentMap headers ^WebSocketConnectionCallback callback]
(let [handler (-> (WebSocketProtocolHandshakeHandler. callback)
(.addExtension (PerMessageDeflateHandshake. true 6)))]
(when headers
(set-headers (.getResponseHeaders exchange) headers))
(.handleRequest handler exchange)))
If I remove the extension, I can no longer repro it. But I am not sure that the extension is necessarily the cause.
How would you go about debugging this? All thoughts much appreciated> How would you go about debugging this?
Without being familiar with Undertow at all - by reading through its sources, finding the place(s) where the data is actually being sent, and putting a conditional breakpoint there that triggers only if the data doesn't start with {
.
Noob question: How do your workflow look for > putting a conditional breakpoint there Undertow is written in Java, and I don't have the most experience with this. What are the steps you take to do this? If there' any pointed you have would love it. Thanks @U2FRKM4TW!
I have very little experience with other IDEs, but in IntelliJ IDEA it's trivial: right click on the line number -> add conditional breakpoint.
Heck, okay -- I am going to get idea + cursive and give this a try! Thank you @U2FRKM4TW!
thread 1 prints starts printing a map and thread 2 starts printing, and the output ends up intermingled
I have two chrome tabs with try catch over the message
handler:
this._ws.onmessage = this._wsOnMessage;
// ...
_wsOnMessage = (e) => {
try {
this._handleReceive(JSON.parse(e.data.toString()));
} catch (error) {
debugger;
}
};
To repro, I move my cursor around until this error triggers and a debugger opens.(Added more examples in a https://stackoverflow.com/questions/78444592/undertow-seems-to-send-corrupt-messages-when-enabled-with-permessage-deflate version of this question)
Could you have multiple threads doing json encoding to the same output stream, and then data from that steam is going out over the websocket?
Could there be a big in the server side code where everything is writing to the same websocket
The way you are doing the per message deflate thing is done, you are create a new instance per request, which seems like it would be the correct way to use such a thing, but maybe it needs to be created once and re-used
https://undertow.io/javadoc/1.3.x/io/undertow/websockets/extensions/PerMessageDeflateHandshake.html the boolean argument is if the code is running as a client or server, server is false
https://issues.redhat.com/browse/UNDERTOW-684?focusedCommentId=13203543&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel looks similar but is marked resolved
I do think you're onto something about multiple threads writing to the same shared resource. I don't think it's the json encoding. This is where I do it:
(defn send-json!
"Serializes `obj` to json, and sends over a websocket."
[obj ws-conn]
(let [obj-json (cheshire/generate-string obj)
p (promise)
_ (WebSockets/sendText
^String obj-json
^WebSocketChannel ws-conn
(proxy [WebSocketCallback] []
(complete [ws-conn context]
(deliver p nil))
(onError [ws-conn context throwable]
(deliver p throwable))))
ret @p]
(when (instance? Throwable ret)
(throw ret))))
Interestingly, if I do the following, I can still repro it:
(defn send-json!
"Serializes `obj` to json, and sends over a websocket."
[obj ws-conn]
(let [obj-json (if (#{:refresh-presence :set-presence-ok} (:op obj))
(cheshire/generate-string {"op" "ping" "id1" (java.util.UUID/randomUUID)
"id2" (java.util.UUID/randomUUID)
"id3" (java.util.UUID/randomUUID)
"id4" (java.util.UUID/randomUUID)
"id5" (java.util.UUID/randomUUID)
"id6" (java.util.UUID/randomUUID)
"id7" (java.util.UUID/randomUUID)})
(cheshire/generate-string obj))
p (promise)
_ (WebSockets/sendText
^String obj-json
^WebSocketChannel ws-conn
(proxy [WebSocketCallback] []
(complete [ws-conn context]
(deliver p nil))
(onError [ws-conn context throwable]
(deliver p throwable))))
ret @p]
(when (instance? Throwable ret)
(throw ret))))
But if I do not use UUIDs, I can't repro it.
I am running send-json!
in parallel, so there may be some corruption going on. I try changing pmap
to mapv
though and no dice.https://github.com/undertow-io/undertow/blob/master/examples/src/main/java/io/undertow/examples/websockets_extension/WebSocketServer.java#L61 shows creating a single PerMessageDeflateHandshake object and not a new one per request, and shows passing false instead of true
Maybe just try throwing a (locking #'send-json! ...)
around the body of send-json! and see if that eliminates the corruption
sendText is going to write to a buffer, then call a callback when the buffer has been flushed out, so it seems plausible that writing to the buffer from multiple threads could be unsafe
FWIW I'm also using this in shadow-undertow (and therefore shadow-cljs). although only ever creating on instance of the handler per server, so maybe try that? https://github.com/thheller/shadow-undertow/blob/bf84dab4989f21601c52449a36fda4dabe2e067a/src/main/shadow/undertow.clj#L518-L520
Update:
I tried a hack, where I cached the handler
, but the issue persisted.
However : I can't repro anymore whenever I add locking
!
(def lock (Object.))
(defn send-json!
"Serializes `obj` to json, and sends over a websocket."
[obj ws-conn]
(locking lock
(let [obj-json (->json obj)
_ (clojure.tools.logging/infof "Sending JSON locked: %s" obj-json)
p (promise)
_ (WebSockets/sendText
^String obj-json
^WebSocketChannel ws-conn
(proxy [WebSocketCallback] []
(complete [ws-conn context]
(deliver p nil))
(onError [ws-conn context throwable]
(deliver p throwable))))
ret @p]
(when (instance? Throwable ret)
(throw ret)))))
This is curious. According to https://lists.jboss.org/archives/list/[email protected]/thread/DA6AKJRTGYWJZRKPXNYVLZ3RPSJ24RDC/ WebsocketChannel should be thread-safe, but I see what you mean about writing to this buffer from multiple threads.
Maybe what I can do is to lock per channel
or something. I notice @U05224H0W has a single loop that does send
too.it maybe that websockets are no longer thread safe once you add the deflate extension thing
Ah! That could be it. Thank you @U0NCTKEV8