Fork me on GitHub
#clojure
<
2024-05-07
>
escherize02:05:19

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

teodorlu05:05:07

Perhaps you could pair clj-reload with a file watcher such as https://github.com/nextjournal/beholder?

👀 1
👍 2
Danil Shingarev09:05:34

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,

escherize15:05:55

Thanks, @U3X7174KS and @U06G9HMLGPM! I wanted to make sure I wasn’t reinventing the wheel here 🙂

❤️ 1
escherize15:05:12

I knew about hawk, but not beholder. interesting.

Danil Shingarev15:05:45

what’s wrong with reinventing the wheel? 😄

😅 1
teodorlu19:05:56

Any time! Happy to aid in both wheel-finding and wheel-inventing, whatever floats your boat.

escherize19:05:40

uhm, boats dont have wheels ☝️🤓

😁 1
teodorlu19:05:50

Good point! I'll have to invent some better metaphors. "whatever gets your engine humming"?

escherize19:05:24

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.

trollface 2
mogverse05:05:35

Is there a good resource on designing DSLs and identifying domain language of your business? With Clojure/Lisp/ Or any other language?

daveliepmann15:05:39

I don't have anything specific but this sounds like the Racket community's kind of jam

daveliepmann17:05:36

love his writing!

Ludger Solbach17:05:13

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.

mogverse09:05:42

How different is creating these DSLs with Racket vs say using instaparse with Clojure?

daveliepmann09:05:42

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.

Ludger Solbach14:05:34

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..

agata_anastazja (she/her)13:05:45

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?

oyakushev13:05:17

I think, instead of alts! you can use poll!: https://clojuredocs.org/clojure.core.async/poll!

oyakushev13:05:10

But AFAIK, neither poll! , nor alts! can retroactively consume the values from the channel if the control flow already went forwarrd.

💜 1
🎉 1
agata_anastazja (she/her)13:05:48

and you're right! poll is a better fit for me

👍 1
oyakushev13:05:57

Note that your modified code will spin endlessly and without blocking, so this is probably not what you have in mind.

oyakushev13:05:15

Sorry, I see now that it will throttle on c'

agata_anastazja (she/her)13:05:17

ehh... true, needs some more thought

agata_anastazja (she/her)13:05:51

but the while loop will keep going

oyakushev13:05:15

I think you will still need alts! to differentiate between no value and closed channel.

stopa17:05:31

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

p-himik17:05:02

> 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 {.

❤️ 1
stopa17:05:46

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!

p-himik17:05:24

I have very little experience with other IDEs, but in IntelliJ IDEA it's trivial: right click on the line number -> add conditional breakpoint.

stopa17:05:08

Heck, okay -- I am going to get idea + cursive and give this a try! Thank you @U2FRKM4TW!

👍 1
hiredman18:05:09

how are you printing out the data you are getting from the websockets?

hiredman18:05:44

because it looks sort of like synchronized printing out from multiple threads

hiredman18:05:22

thread 1 prints starts printing a map and thread 2 starts printing, and the output ends up intermingled

hiredman18:05:38

(and 2 tabs => 2 websockets => 2 threads servicing the websockets)

stopa19:05:40

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.

hiredman19:05:47

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?

hiredman19:05:30

Could there be a big in the server side code where everything is writing to the same websocket

hiredman19:05:39

Does it look more and more corrupt the more tabs you open?

hiredman19:05:02

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

hiredman19:05:31

And true should be false

hiredman19:05:30

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

stopa19:05:04

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.

stopa19:05:24

(I tried changing client? between true and false and got no benefit there either)

hiredman19:05:31

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

hiredman19:05:20

Maybe just try throwing a (locking #'send-json! ...) around the body of send-json! and see if that eliminates the corruption

hiredman19:05:33

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

hiredman19:05:20

I guess mapv should have checked that though

thheller19:05:42

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

❤️ 1
stopa20:05:39

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.

hiredman20:05:10

it maybe that websockets are no longer thread safe once you add the deflate extension thing

stopa20:05:02

Ah! That could be it. Thank you @U0NCTKEV8