async icon indicating copy to clipboard operation
async copied to clipboard

concurrently' looks a tad leaky

Open treeowl opened this issue 5 years ago • 3 comments

Both thread IDs are retained in case they're needed to cancel the corresponding thread (s), which as I understand it keeps the threads live. But of course there's no need to cancel a thread if it's already finished! One option might be to use something richer than an Int to keep track of threads, like Starting | Started !ThreadId | Done. Now the thread IDs can live only in an IORef, so each thread can remove itself when it's done or killed.

treeowl avatar Oct 03 '19 01:10 treeowl

Perhaps there's a simpler way. I haven't looked at whether this would work for concurrently', but I think it would work for its applications. For example, I think we could use something vaguely like

data Status a = Started !ThreadId | Done !(Either SomeException a)

concurrently :: IO a -> IO b -> IO (a, b)
concurrently left right = do
  mva <- newEmptyMVar
  ta <- forkIO $ (do
    ar' <- left
    swapMVar mva $ Done (Right ar'))
       `catch` \e -> swapMVar mva $ Done (Left e)
  putMVar mva (Started ta)
  br <- right `onException` takeMVar mva >>= \case
    Started tid -> throwTo tid ThreadCancelled
    Done{} -> pure ()
  ar <- takeMVar mva
  pure (ar, br)

Unlike the current code, nothing is uninterruptible.

treeowl avatar Oct 03 '19 02:10 treeowl

Is this really a problem? A finished ThreadId will only keep alive the topmost stack chunk, which is 1KB. I'm worried the cost of fixing this "leak" could be worse than the leak itself.

simonmar avatar Oct 03 '19 07:10 simonmar

Dunno! I'll let you know if I come up with a solution I like. This stuff is terribly subtle.

treeowl avatar Oct 03 '19 07:10 treeowl