What's ki's policy on resources that go out of scope whilst thread is still running? #37
Replies: 3 comments 5 replies
-
👋 Hi, let's see... I'm not sure there's much we can do in
That sounds right, but I'm not yet sure it is necessary to go that far. Suppose we only say "take care to ensure resources are valid for the duration of any forked threads" but not actually disallow programs like your example. Do we gain anything by taking that stance? Or, put differently, does your example program (or any other example program you can think of) actually need to sequence the resources in this way: scoped \scope -> do
withThing \thing -> do
... rather than: withThing \thing -> do
scoped \scope -> do
... If not, then yeah, I'd agree with "Don't use a Scope obtained outside a resource-acquiring block within that block" :) |
Beta Was this translation helpful? Give feedback.
-
Thanks! Good point about There is at least one resource we know whose lifetime exactly corresponds to the period in which execution is within its "bracket": a ki scoped \scope1 -> do
fork scope1 do
scoped \scope2 -> do
fork scope2 do
-- call this thread "T"
stuff
fork scope1 do
-- This thread will continue to
-- run even when its "parent
-- thread", T, has terminated.
something Now Anyway, I don't think I have a particular point. I'm just vaguely hinting at some intuition I have and wondering if it aligns with what anyone else is thinking. A concrete questionI'll follow up with a simpler concrete question: is there a good example of something that ki can do that |
Beta Was this translation helpful? Give feedback.
-
See below. The
{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE BlockArguments #-}
import Control.Concurrent
import Control.Concurrent.Async (withAsync, wait)
import Control.Monad (when)
import Data.Maybe (isJust)
staggeredSpawner :: [IO ()] -> IO ()
staggeredSpawner [] = pure ()
staggeredSpawner (x : xs) =
withAsync
(threadDelay 250_000 *> staggeredSpawner xs)
(\thread -> x *> wait thread)
happyEyeballs :: [IO (Maybe a)] -> IO (Maybe a)
happyEyeballs actions = do
resultVar <- newEmptyMVar
let worker action = do
result <- action
when (isJust result) do
_ <- tryPutMVar resultVar result
pure ()
withAsync
( do
staggeredSpawner (map worker actions)
tryPutMVar resultVar Nothing
)
(\_ -> takeMVar resultVar)
Hmm, I'm not sure what you mean here. If you're only spawning one thread |
Beta Was this translation helpful? Give feedback.
-
What's ki's policy on resources that go out of scope whilst a thread is still running?
ki's approach to structured concurrency is very nice because it is strictly more general than
async
'swithAsync
whilst maintaining a number of desirable invariants. However, that generality does also allow some pathological behaviour that is impossible inwithAsync
.Consider the program below. I open a
scoped
block, then awithFile
block to obtain aHandle
, and then forked a thread that uses theHandle
. However, it's possible for thewithFile
block to finish before the thread has finished using theHandle
, leading to a run time error.There are some possible responses I can think of
"
withFile
isn't safe anyway, so it doesn't matter"True, but
withFile
does have some guarantees, such as, if the body returns()
, and it only ever launches threads withwithAsync
, then theHandle
doesn't leak. (A version ofwithAsync
for Bluefin would do even better.)"You can see that a
Scope
obtained outsidewithFile
is used withinwithFile
, which is obviously dangerous"True. It is a nice property of ki that you can observe this syntactically. (It's a style that Bluefin uses too.)
So does ki have a particular policy around this? Maybe it's
That seems fine. I guess it's disappointing that the most general concurrency construct I know of that doesn't have this problem (
withAsync
) is so much less general, but perhaps that's just life.Beta Was this translation helpful? Give feedback.
All reactions