§Weird behavior

Encountered one weird problem while reading Parallel and Concurrent Programming in Haskell.

import Control.Concurrent
import Control.Exception
import qualified Data.Map as Map
import Text.Printf
import Control.Monad
import System.IO
import qualified Data.ByteString as B

import Network.HTTP
import Network.Browser
import Network.URI
import Data.ByteString (ByteString)

getURL :: String -> IO ByteString
getURL url = do
  Network.Browser.browse $ do
    setCheckForProxy True
    setDebugLog Nothing
    setOutHandler (const (return ()))
    (_, rsp) <- request (getRequest' (escapeURIString isUnescapedInURI url))
    return (rspBody rsp)
  where
    getRequest' :: String -> Request ByteString
    getRequest' urlString =
      case parseURI urlString of
        Nothing -> error ("getRequest: Not a valid URL - " ++ urlString)
        Just u -> mkRequest GET u

type Async a = MVar (Either SomeException a)

async :: IO a -> IO (Async a)
async action = do
  var <- newEmptyMVar
  forkIO $ do
    r <- try action
    putMVar var r
  return var

wait :: Async a -> IO a
wait a = do
  -- r <- takeMVar a
  r <- readMVar a
  case r of
    Left e -> throwIO e
    Right a -> return a

waitCountDown :: Async a -> Integer -> IO a
waitCountDown m 1 = do wait m
waitCountDown m count = do
  r <- takeMVar m
  case r of
    Left e -> waitCountDown m (count-1)
    Right a -> return a


waitAny :: [Async a] -> IO a
waitAny as = do
  m <- newEmptyMVar
  let waitFork a = forkIO $ do r <- readMVar a; putMVar m r
  mapM_ waitFork as
  -- waitCountDown m $ fromIntegral $ length as
  wait m

-- sites = ["http://googlegoogle.com/"]
sites = reverse ["http://www.googlegoogle.com/",
  "http://auportal.herokuapp.com",
  "http://auportal.herokuapp.com",
  "http://www.google.se/"]

download url = do
  r <- getURL url
  return (url, r)

main = do
  as <- mapM (async . download) sites
  (url, r) <- waitAny as
  printf "%s was first (%d bytes)\n" url (B.length r)

The code is actual correct, but it sometimes is blocked, as shown in my console output. The weird thing is that runghc seems to work fine. I even suspected that there’s some magic in runghc, so I checked the source of runghc, and the it more or less equivalent to ghc -e Main.main test.hs.

concurrent » ghc test.hs ; repeat 10 ./test
[1 of 1] Compiling Main             ( test.hs, test.o )
Linking test ...
test: thread blocked indefinitely in an MVar operation
test: thread blocked indefinitely in an MVar operation
test: thread blocked indefinitely in an MVar operation
test: thread blocked indefinitely in an MVar operation
test: thread blocked indefinitely in an MVar operation
test: thread blocked indefinitely in an MVar operation
test: thread blocked indefinitely in an MVar operation
test: user error (openTCPConnection: host lookup failure for "www.googlegoogle.com")
test: thread blocked indefinitely in an MVar operation
test: thread blocked indefinitely in an MVar operation

concurrent » repeat 10 runghc test.hs
http://www.google.se/ was first (17942 bytes)
http://www.google.se/ was first (17926 bytes)
http://www.google.se/ was first (17950 bytes)
http://auportal.herokuapp.com was first (3190 bytes)
http://auportal.herokuapp.com was first (3190 bytes)
http://www.google.se/ was first (17942 bytes)
http://www.google.se/ was first (17956 bytes)
http://www.google.se/ was first (17893 bytes)
http://www.google.se/ was first (17901 bytes)
http://www.google.se/ was first (17941 bytes)

With no luck on runghc, I switched to the doc for Control.Concurrent, hoping to find something helpful. All of the sudden, I stumbled upon readMVar, and the incompatible note is what I am after. Replacing readMVar with takeMVar resolves the blocking issue immediately. Running the original code in GHC 7.8 doesn’t have this “blocked indefinitely” exception as well. Then, I could say that I am luck (unluck) enough to be caught by one bug in GHC.

§Why the interpreter works

How come runghc succeeds? My postulation would be contention inside the interpreter is not so high, so this error is not exposed.