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))
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.
the thread threadpool caches and re-uses OS threads, so new threads created will be minimal
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.
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
> 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?
yeah as long as the thread is occupied, it cannot be released back to the threadpool
if that's what you're doing, you probably want to set a hard limit to the number of threads you spawn
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.
ordinarily, you use thread here and there inside of go blocks when you need to do some IO
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)
> 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
depends on what stream-ready-fn does I suppose
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
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
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.
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.
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
Taking is "blocking", but only up to an element being available. Here I'm guessing it blocks only up to the stream being ready.
I would be more worried that the call to thread isn't wrapped like (
lol read your initial response, hiredman, and was like wat
😕
the edit makes much more sense
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 ?
Or it's possible Pedestal assumes SSEs are relatively short lived? Or that a single instance won't be handling 500+ clients. Who knows
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
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
Really, really good calls, thank you So, probably grab a channel and send it somewhere sounds like a way to go here
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.
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.
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.