paperduck
paperduck

Reputation: 1195

Concurrent (multithreaded) program hangs unless I print something

I have a function that I fork off into a thread using forkIO. The function runs in a ReaderT monad transformer so that I can pass in a read-only configuration record:

main :: IO ()
main = do 
    ...
    forkIO $ runReaderT watcher config

The watcher function watches an MVar using tryTakeMVar (I don't want it to block.) The MVar is stored in the config and is called "drawer" because it behaves like a transaction drawer between main and the thread that watcher is watching in, basically a skip channel.

printThing has a signature of printThing :: Thing -> ReaderT Config IO () and calls putStrLn to print a Thing.

watcher :: ReaderT Config IO ()                                                                                                                                                                       
 watcher = do
     cfg <- ask
     mNewThing <- liftIO $ tryTakeMVar $ drawer cfg
     case mNewThing of
         Nothing -> do
            --liftIO $ putStr ""  -- uncommenting this helps
            watcher
         Just newThing -> do
             printThing newThing
             watcher

The problem is that the program hangs when it runs. It seems to be stuck in a loop. Calling putStr "" in main doesn't help, HOWEVER, calling putStr "" inside watcher does trigger the thread -- it starts spinning and printing out Things as expected.

All I can figure is that I'm getting bitten by laziness, but I'm not sure where. I've tried using $! where possible.

I do IO actions in certain conditions of watcher, but not all of them. Is that the problem? I need to do IO actions in ALL conditions branches?

If it helps, I didn't have this problem before I wrapped everything up in the ReaderT transformer. I was just passing config around as an argument.

Upvotes: 0

Views: 137

Answers (1)

Daniel Wagner
Daniel Wagner

Reputation: 152682

Despite the text in your question, I recommend that you let watcher block. It is quite rare indeed to need non-blocking operations on MVars; usually wanting it is a sign you haven't quite internalized the "fork everything" mentality. So:

watcher :: ReaderT Config IO ()
watcher = do
    cfg <- ask
    newThing <- liftIO . takeMVar $ drawer cfg
    printThing newThing
    watcher

We can separately address a question of the form "How do I achieve effect X, that seems to me to need non-blocking operations, while only using blocking operations?" if you will write up a separate question with some details about effect X.

Side note: I'd be tempted to write the above in the following way, which has the same meaning but appeals to me more aesthetically:

watcher = forever (asks drawer >>= liftIO . takeMVar >>= printThing)

Upvotes: 3

Related Questions