Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deadlock with race #93

Closed
lehins opened this issue Apr 8, 2019 · 4 comments
Closed

deadlock with race #93

lehins opened this issue Apr 8, 2019 · 4 comments

Comments

@lehins
Copy link

@lehins lehins commented Apr 8, 2019

This simple code results in a deadlock. Solution is to stick a yield inside the otherwise clause of the check function, but that's not always possible, since check can be pure function with a loop in it.

import Control.Concurrent.Async

someFunc :: IO ()
someFunc = do
  w <- pooledCheck 2
  if w == 2
    then putStrLn "Success"
    else putStrLn "Bug in implementation"

check :: Int -> Int -> IO Int
check step = go
  where go n | even n = return n
             | otherwise = go (n + step)

pooledCheck :: Int -> IO Int
pooledCheck step = do
  let f = check step
  either id id <$> race (f 1) (f 2)

Here is a repo that can be used to reproduce the issue: https://github.com/lehins/async-bug-repro running stack run should result in a deadlock

@simonmar

This comment has been minimized.

Copy link
Owner

@simonmar simonmar commented Apr 8, 2019

Yes, this is a known limitation in GHC's concurrency implementation. The workaround is to use -fno-omit-yields.

@simonmar simonmar closed this Apr 8, 2019
@chrisdone

This comment has been minimized.

Copy link

@chrisdone chrisdone commented Apr 9, 2019

@simonmar So, is this because normally there is a preemptive timer that is supposed to introduce yielding in non-allocating, non-I/O-doing threads? But -fomit-yields turns that off by default? Is that an efficiency trade-off?

@simonmar

This comment has been minimized.

Copy link
Owner

@simonmar simonmar commented Apr 11, 2019

Not quite - GHC's concurrency implementation is quasi-cooperative, that is, threads must voluntarily yield to the scheduler. The voluntary nature is normally invisible to the programmer because it happens at every heap allocation, and those normally happen often enough for it to look like preemption. But if you happen to write a loop that runs for a long time without allocating any memory, it won't be preempted at all - indeed the GC won't be able to run, and all the other threads in the system will be blocked (even when using +RTS -N). The -fno-omit-yields flag inserts yield points in enough places that this can't happen, at the expense of some performance.

One thing we haven't done is try to optimise -fno-omit-yields to reduce the overhead, that would be a nice project for someone.

@chrisdone

This comment has been minimized.

Copy link

@chrisdone chrisdone commented Apr 11, 2019

@simonmar Thanks for the explanation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.