<div dir="ltr"><div>I hadn't come across structured concurrency before, that was an interesting read.</div><div><br></div><div>A quick google turns up a Haskell structured concurrency package, but it seems to be relatively new: <a href="https://hackage.haskell.org/package/ki">https://hackage.haskell.org/package/ki</a></div><div><br></div><div>Michael<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Mar 10, 2022 at 5:59 PM Olaf Klinke <<a href="mailto:olf@aatal-apotheke.de">olf@aatal-apotheke.de</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On Thu, 2022-03-10 at 07:44 +0000, Oliver Charles wrote:<br>
> On Wed, 9 Mar 2022, at 7:45 PM, Olaf Klinke wrote:<br>
> > That is indeed a neat pattern. What are its advantages/disadvantages? <br>
> > The main thread in my case is the (Yesod) webserver. Perhaps Yesod does<br>
> > exactly what you propose, internally.<br>
> <br>
> Mostly that one doesn't have to think about linking threads together, that just comes by design. If you want "these things must always be running", I find it's a nice fit. Also, this pattern implements the "structured concurrency" paradigm, which I'm a big fan of (see <a href="https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/" rel="noreferrer" target="_blank">https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/</a>)<br>
> <br>
> > Your idiom would be something like this?<br>
> > <br>
> > main = foldr race_ (warp port site) workers<br>
> <br>
> Yes, something like that.<br>
> <br>
> > I also do want to include the possibility of a child (daemon) thread to<br>
> > crash without crashing the webserver (hence my original question),<br>
> > which your race solution does not cater for. In fact I will have a mix<br>
> > of perpetually running actions and actions that are fired on command of<br>
> > the user and then exit gracefully. <br>
> <br>
> Right, if you _expect_ the daemon to terminate, then this isn't a good fit. However, I would still be striving to get some kind of structured concurrency solution. To that end, I would probably have some over-arching daemon that has a shared job queue (e.g., an STM TChan), and web requests can write to this TChan (maybe with a TMVar to fill in responses). Then, the worker daemon would pop jobs off this TChan and itself spawn new threads and manage the lifetime of them. This gives you the best of both worlds - structured concurrency in main, and ephemeral worker threads.<br>
> <br>
> Hope this helps,<br>
> Ollie<br>
<br>
Hmm, but this over-arching daemon (nursery, as it is called in the blog<br>
post) watching over the queue *is* my webserver. Instead of a TChan it<br>
has routes that correspond to start and pause commands of specific<br>
daemon threads. Inter-thread signalling is indeed handled via TVars,<br>
that's what they're for. It would be cool to have a TVar or TChan that<br>
is read-only for one side and write-only for the other. And thank to<br>
Control.Concurrent.Async.link I have full control over which exceptions<br>
get propagated from child to parent. The library designers probably<br>
should not have made async and link two distinct functions, because<br>
that enables the programmer to disregard those exceptions. <br>
Should we be porting Trio to Haskell? Or does Haskell offer enough<br>
abstraction already? <br>
There are some points in the blog post that I disagree with. If any<br>
function can fork a concurrent process, not all is lost. Because of<br>
immutable values we pretty much know what can be touched by that<br>
concurrent process. And because of that, often we can ignore how long<br>
the concurrent process runs because it can not affect our linear code. <br>
<br>
I should have read the Async documentation more carefully. <br>
<br>
startThread :: MyThread -> IO ()<br>
startThread (action,var) = withAsync action (putMVar var)<br>
<br>
immediately cancels the thread because putMVar returns. <br>
Hence one must use async instead of withAsync if the main thread can<br>
not be passed to withAsync. The pattern similar to your race would be: <br>
<br>
foldr (\a b -> withAsync a (const b)) mainThread daemons<br>
<br>
which, unlike race, does not kill the mainThread if one of the daemons<br>
crashes, but cancels the daemons if the mainThread terminates. <br>
<br>
Olaf<br>
<br>
_______________________________________________<br>
Haskell-Cafe mailing list<br>
To (un)subscribe, modify options or view archives go to:<br>
<a href="http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe" rel="noreferrer" target="_blank">http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe</a><br>
Only members subscribed via the mailman list are allowed to post.</blockquote></div>