Skip to content

Eq instance for ThreadId too coarse

Two Eq-equal ThreadIds may refer to two different threads.

It seems that for Eq and Show purposes ThreadId relies on a 32 bit counter that wraps around if forkIO is called often enough. Yet, killThread (fortunately) does not mix up two handles that look equal according to Eq and Show.

The behaviour is illustrated by the following program:

import Control.Concurrent
import Control.Monad

main :: IO ()
main = do
  t0 <- myThreadId
  (`mapM_` [0 :: Integer .. ]) $ \i -> do
     t <- forkIO $ return ()

     when (i `mod` 500000000 == 0) $ print t

     when (t == t0) $ do
       putStrLn $ "Killing " ++ show t ++ "..."
       killThread t

       threadDelay 1000000

       putStrLn $ "Killing " ++ show t0 ++ "..."
       killThread t0
$ ghc -O Bug.hs && ./Bug
[1 of 1] Compiling Main             ( Bug.hs, Bug.o )
Linking Bug ...
ThreadId 2
ThreadId 500000002
ThreadId 1000000002
ThreadId 1500000002
ThreadId 2000000002
ThreadId -1794967294
ThreadId -1294967294
ThreadId -794967294
ThreadId -294967294
Killing ThreadId 1...
Killing ThreadId 1...
Bug: thread killed
$ echo $?
1

This reveals that t and t0 refer to two different threads, although t == t0: it seems that killThread t kills the thread just forked, whereas killThread t0 kills the main thread.

If both t and t0 referred to the thread just forked, the program would continue to run. If both referred to the main thread, the program would already terminate at the first invocation of killThread.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information