Quirky behaviour of GHC.Conc.threadStatus
Summary
The behaviour of GHC.Conc.threadStatus is hard to explain, and is probably not as intended.
Steps to reproduce
It appears to be the case that if a victim thread is killed by throwTo
before it gets a first chance to run, then we get a different final thread status than if the victim thread did get a chance to run.
Here's an example:
import GHC.Conc
import Control.Exception
import Control.Monad
main :: IO ()
main = do
tid <- forkIO (forever yield)
yield
throwTo tid DivideByZero
yield
print =<< threadStatus tid
tid' <- forkIO (forever yield)
throwTo tid' DivideByZero
yield
print =<< threadStatus tid'
This reports
$ ./ThreadStatus
ThreadStatus: divide by zero
ThreadFinished
ThreadDied
Expected behavior
The thread status after the thread has terminated should be the same for both cases, whether the victim thread had a chance to run or not.
Furthermore, one might reasonably expect that both cases should result in ThreadDied
, rather than ThreadFinished
. Indeed it appears (from superficial testing) that the only case that reports ThreadDied
is the case of an async exception to a thread that has not yet been scheduled. From the docs
ThreadFinished -- ^ the thread has finished
ThreadDied -- ^ the thread received an uncaught exception
one would expect that the distinction should be terminated by exception (sync or async) vs terminated by return. But the reality appears to be that all normal sync and async exceptions result in ThreadFinished
and it's just the "never ran" case that results in ThreadDied
.
See also issue #21195 about the documentation.
See also confusion over the semantics of threadStatus
in io-sim
: https://github.com/input-output-hk/io-sim/pull/19
Environment
- GHC version used: 8.10.7 and 9.2.4 (same behaviour on both)