[GHC] #15136: High CPU when asynchronous exception and unblocking retry on TVar raced
GHC
ghc-devs at haskell.org
Thu May 10 00:37:04 UTC 2018
#15136: High CPU when asynchronous exception and unblocking retry on TVar raced
-------------------------------------+-------------------------------------
Reporter: nshimaza | Owner: (none)
Type: bug | Status: new
Priority: highest | Milestone: 8.6.1
Component: Runtime | Version: 8.4.2
System |
Keywords: | Operating System: Unknown/Multiple
Architecture: | Type of failure: Runtime crash
Unknown/Multiple |
Test Case: | Blocked By:
Blocking: | Related Tickets:
Differential Rev(s): | Wiki Page:
-------------------------------------+-------------------------------------
Detail: https://github.com/nshimaza/race-tmvar-async-exception
Runtime falls into high CPU under racing condition between async exception
and unblocking retry on TVar.
* Reproduces with +RTS -Nx where x > 1
* Does NOT reproduce with +RTS -N1
* Program stalls at `killThread`
* High CPU based on given -Nx
* CPU won't be 100% if you gave x smaller than available hardware
threads of your platform.
* Does NOT reproduce if TVar/retry is replaced by MVar
* Reproduced with GHC 8.4.2 (macOS High Sierra (10.13.4))
* Reproduced with GHC 8.4.2 (Docker for Mac Version 18.03.1-ce-mac65)
* Reproduced with ghc-8.5.20180506 (Docker for Mac Version 18.03.1-ce-
mac65)
Minimal reproducing code here. (You can find more verbose code on the
above github repo.)
{{{#!hs
main :: IO ()
main = do
let volume = 1000
forM_ [1..1000] $ \i -> do
putStrFlush $ show i ++ " "
-- Spawn massive number of threads.
threads <- replicateM volume $ do
trigger <- newTVarIO False
tid <- forkIO $ void $ atomically $ do
t <- readTVar trigger
if t then pure t else retry
pure (trigger, tid)
-- Make sure all threads are spawned.
threadDelay 30000
-- Let threads start to exit normally.
forkIO $ forM_ threads $ \(trigger, _) -> threadDelay 1 *>
atomically (writeTVar trigger True)
-- Concurrently kill threads in order to create race.
-- TMVar operation and asynchronous exception can hit same thread
simultaneously.
-- Adjust threadDelay if you don't reproduce very well.
threadDelay 1000
forM_ threads $ \(_, tid) -> do
putCharFlush 'A'
killThread tid -- When the issue reproduced, this
killThread doesn't return.
putCharFlush '\b'
}}}
This program intentionally creates race condition between asynchronous
exception
and unblocking operation of `retry` on TVar. From one side, a `writeTVar
trigger True` is attempted from external thread while target thread is
blocking
at `retry` on the same `TVar`. On the other side, an asynchronous
exception
`ThreadKilled` is thrown by yet another external thread to the same target
thread.
In other word, it attempts to kill a thread about to unblock.
I guess when the above two operation hit the same thread at the same time
in
parallel in SMP environment, GHC runtime falls into high CPU.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/15136>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list