Fork me on GitHub
#clojure-uk
<
2021-03-31
>
dharrigan07:03:36

Good Morning!

danm08:03:13

Moin moin

danm08:03:06

Do any folks here have much experience with core.async? I've dropped a query into #core-async about something that sounds sensible to me, but might just be utterly insane because I've not used it much before and haven't properly grokked something. Could do with eyes on, and I suspect most of the rest of the channel is currently in a timezone where they're asleep 😉

mccraigmccraig09:03:36

had a quick look from a general async perspective @carr0t (i don't use core.async so much, but use manifold-streams lots in similar ways) and it looks generally sane. the only thing i would observe is that you probably want an agent rather than an atom of channel registrations - since atoms will retry swap! actions under load

Atomically swaps the value of atom to be:
(apply f current-value-of-atom args). Note that f may be called
multiple times, and thus should be free of side effects.  Returns
the value that was swapped in.

mccraigmccraig09:03:18

for similar cases we use a slightly different architecture - api handler async sends an event onto a kafka topic and async subscribes to a gnatsd topic for response, separate kafka-streams app consumes event in kafka's synchronous way, when finished puts message on a gnatsd topic, which wakes the api handler to continue - but it adds another architectural component, which is probably worth avoiding if you don't have a need for it (we did have a need - we drive websockets for large numbers of users, and kafka doesn't play with large numbers of topics)

danm09:03:49

Does that not run the risk of 2 assoc calls (for different keys) running in parallel against the agent and one of them getting lost in the final version? I mean, if we fail to assoc because state has changed or similar we have to retry before we can continue anyway

mccraigmccraig09:03:05

but, as long as your atom is just a registration of channels with no side effect it's fine

danm09:03:07

I've not used agents before, so I'm wary of using them 'wrong' 😉

danm09:03:51

I guess the validator could check that the key exists?

mccraigmccraig09:03:18

agent serializes the actions

mccraigmccraig09:03:43

so there will never be two calls running in parallel - they will always run one after the other... but i'm overthinking it - if all your swap! is doing is assoc or dissoc of a chan with no side-effects then it's fine

mccraigmccraig09:03:17

i'm just a bit scarred by a very non-obvious bug i had which turned out to be because a side-effecting swap! fn was getting repeatedly executed 😬 - but the issue was the side-effect, not the atom

danm10:03:28

Yeah, I guess there's not much difference between repeated swap! assoc calls until it works, and send assoc followed by await.

mccraigmccraig10:03:16

yeah, the differences only become apparent when there's a side effect. if you are already async, you don't need to await a send though - you put your continuation action (resolve a promise, send to a chan etc) at the end of your send action, so you get guaranteed serialization of the state change without any blocking

mccraigmccraig10:03:34

having never really used the STM stuff, serializing side-effecting state updates has been the only time i've ever used an agent in anger

danm12:03:31

Potentially I don't even need core.async, as @cursork has pointed out. The respond-fn when using ring-jetty-adapter with :async? true already has the context to correctly match up the request. So I could just put the respond-fn for a given request directly into the atom, and have them all executed by the consumer thread. I don't know how much they'd block on sending the response to a servlet over a buffer mind you, but it should be minimal

danm12:03:40

And probably basically the same as the channel

mccraigmccraig12:03:23

oh, cool - that's better than introducing a channel

danm12:03:11

We've ended up going back on that after some more talking it through 😉 The channel gives us a nice mechanism for sending a 504 response to the client and cleaning up expired requests from the atom when a timeout is hit, which putting the entire respond-fn into the atom and responding via the consumer thread doesn't. We could still clean up when we eventually got the message response (assuming a major error downstream didn't mean we never got one), but we wouldn't be able to nicely send the client a 504 if that was a long time after the HTTP timeout

mccraigmccraig14:03:04

how are you doing your async @carr0t?

danm14:03:23

What do you mean?

mccraigmccraig14:03:37

as in are you using raw callbacks, manifold, core.async etc

danm14:03:14

Current plan is core.async

mccraigmccraig15:03:08

if you are still at the planning stage (and implementing on the JVM) then for promise-like interactions (i.e. things expected to return single values, rather than streams of values), mpenet/auspex and funcool/promesa are both also worth a look (i'm using ztellman/manifold heavily, but i hesitate to recommend that anymore because it's abandonware)

dharrigan15:03:03

I hpoe manifold gets adopted as some point

dharrigan15:03:06

it has nice constructs

Jordan Robinson15:03:33

I just bought ztellman's book actually and really liking it so far

mccraigmccraig16:03:54

i agree @dharrigan - promises/deferreds and streams work really nicely together. it has shortcomings too though, mainly around streams - most obviously that there is no error-state for streams (core.async has the same issue) so you end up having to wrap all the manifold.streams fns with versions which interpret errors on the stream

dharrigan16:03:40

yeah, it could be worked upon to make it better, it would be a shame for it to just stagnate.

harryvederci18:03:44

Hi! I've been lurking here every now and then. I have been waking up at 5am every day to learn Clojure on my own, hoping that one day it would pay off. Today it has: I've signed my first contract for a role as Clojure developer! 🎉

💯 21
🎉 30
👏 9