core-async

maybenot 2024-11-04T18:57:46.961529Z

Another question: is core async thread executing on some sort of a thread pool (probably unbounded)? Does it mean that loop inside the (thread …) will take at least one thread, and eventually this will exhaust the pool/os threads? e.g. for each connected client https://github.com/pedestal/pedestal/blob/master/service/src/io/pedestal/http/sse.clj#L175 (a/thread (loop [] (<!! …) (recur))

2024-11-04T19:06:22.079639Z

yeah thread executes on an unbounded threadpool, and each invocation of thread might run up against the OS thread limit. In your example, exactly 1 OS thread will be used for the body passed to thread.

2024-11-04T19:06:47.840729Z

the thread threadpool caches and re-uses OS threads, so new threads created will be minimal

2024-11-04T19:07:00.174949Z

Each thread call will grab a thread from an unbounded cached thread pool. Theoretically that could exhaust the OS threads yes. By default Linux has a limit of 1024 OS threads per app for example.

2024-11-04T19:08:04.734489Z

it's really unlikely that you run up against the OS thread limit, so you shouldn't worry too much about it, but you're right to keep it in mind

💯 1
maybenot 2024-11-04T19:08:40.336859Z

> the thread threadpool caches and re-uses OS threads, so new threads created will be minimal But not if I start something recursive + blocking inside the thread, no?

2024-11-04T19:09:09.571979Z

yeah as long as the thread is occupied, it cannot be released back to the threadpool

2024-11-04T19:09:30.330429Z

if that's what you're doing, you probably want to set a hard limit to the number of threads you spawn

2024-11-04T19:09:53.138239Z

Right. Like if you have 500 active threads either blocked or in a hot-loop, then that will consume 500 OS threads. But as soon as one completes, it's returned to the pool for another to reuse.

2024-11-04T19:10:29.406239Z

ordinarily, you use thread here and there inside of go blocks when you need to do some IO

maybenot 2024-11-04T19:10:42.857059Z

In the snippet above sse machinery for pedestal starts a thread for user callback, and this callback could be a loop sending stock ticker So I was thinking maybe I’m missing something because it’s kinda shouldn’t scale with this approach (edit. Of course I was missing something: it was noticed in the docs that you shouldn’t loop here)

maybenot 2024-11-04T19:11:37.588469Z

> ordinarily, you use thread here and there inside of go blocks when you need to do some IO Ah, right, so I just could start non blocking process there

👍 1
2024-11-04T19:11:54.397249Z

depends on what stream-ready-fn does I suppose

maybenot 2024-11-04T19:12:54.497259Z

Thanks @potetm and @didibus this was really helpful!

🤘 1
2024-11-04T19:13:54.027569Z

fwiw it looks like it's just doing some initialization in thread . the body of dispatch-loop is a go block: https://github.com/pedestal/pedestal/blob/master/service/src/io/pedestal/http/sse.clj#L108

maybenot 2024-11-04T19:15:48.304239Z

Right, but stream-ready-fn is user provided and in the sample it was using <!! inside it but afaiu all channel readings is indeed inside go block

2024-11-04T19:16:14.762859Z

It seems to just be calling back stream-ready-fn on a separate thread. And that seems to be an event telling the stream is ready to use.

2024-11-04T19:17:00.295929Z

It's on a thread probably because it's user provided. So it doesn't want the code in it to affect its own machinery.

maybenot 2024-11-04T19:17:51.264119Z

Yea, maybe I read it wrong I thought http://pedestal.io/pedestal/0.6/guides/sse.html stream ready here is the stream ready fn

2024-11-04T19:17:53.824689Z

Taking is "blocking", but only up to an element being available. Here I'm guessing it blocks only up to the stream being ready.

2024-11-04T19:19:04.191659Z

I would be more worried that the call to thread isn't wrapped like (

2024-11-04T19:20:15.558289Z

lol read your initial response, hiredman, and was like wat

2024-11-04T19:20:27.199049Z

😕

2024-11-04T19:20:43.090939Z

the edit makes much more sense

2024-11-04T19:21:35.697249Z

I'm not sure from the docs. But I'm guessing this part is important: > Importantly, it is not the job of your callback to send all of the events; your callback is supposed set in motion the concurrent machinery that sends the events. Events will continue to be sent to the client even after the callback returns, until either the client closes the connection, or your code closes the channel. > Which seems to mean, don't loop forever in the callback, just grab the channel that you'll use to send events too maybe ?

2024-11-04T19:22:58.752869Z

Or it's possible Pedestal assumes SSEs are relatively short lived? Or that a single instance won't be handling 500+ clients. Who knows

😅 1
2024-11-04T19:23:24.394469Z

in general it is bad practice to spin up unbounded numebrs of async tasks (futures, go blocks, core.async/thread, whatever) and a rule of thumb to avoid that is to always wait on async tasks completing. not necessarily immediately, so you can spin off ten or something and then wait on them

2024-11-04T19:25:33.382029Z

so a bare call to (thread ...) would be a concern, but there maybe other things in the code, how it is called and used that mitigate that

maybenot 2024-11-04T19:29:33.091079Z

Really, really good calls, thank you So, probably grab a channel and send it somewhere sounds like a way to go here

2024-11-04T19:36:02.904719Z

What I would assume in a scenario like this is that, you have an async callback, so that's using thread on the callback. But for SSE, you have a channel, events to send are buffered into the channel, and a single go block takes from the channel and sends the events in a thread or thread pool.

2024-11-04T19:37:27.451299Z

Like one process is just waiting for events on a channel, and each time one is found, it'll send it to the appropriate client. And then whenever you want to send an SSE, you put to this channel. So the whole thing is multiplexed. One process handles sending all events.

🙌 1
2024-11-04T19:39:36.315519Z

At least that's how I think it should be done. So I'm assuming this is what Pedestal does as well haha. But I didn't spend much time checking the code does in fact do this.

1