0

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.

paperduck
  • 1,175
  • 1
  • 16
  • 26
  • 4
    My guess it that you're getting bitten by the fact that there is [no context switching](https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Concurrent.html#g:4) happening. Can you try inserting calls to [`yield`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Concurrent.html#v:yield) instead of `putStr ""` to see if that works? – typesanitizer Jun 24 '19 at 10:37
  • Are you using threaded or non-threaded runtime? In case of threaded one, are you using more then one capability? IIRC in case of threaded runtime with more then 1 capability enabled, `tryTakeMVar` will yield, otherwise it won't. – Yuras Jun 24 '19 at 11:40
  • 1
    Could be this [known bug](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/bugs.html#bugs-in-ghc) (first item). Try `-fno-omit-yields`, or manually insert a `yield`. – chi Jun 24 '19 at 12:17
  • 3
    Why don't you want `watcher` to block? – Daniel Wagner Jun 24 '19 at 12:45
  • 1
    in fact, `watcher` does block. calling `tryTakeMVar` in an endless loop is just a not so efficient way to block on an MVar. – Michael Jun 24 '19 at 18:04
  • @theindigamer You guessed right, inserting `yield` helps the program run as expected. – paperduck Jun 25 '19 at 03:58
  • @chi `-fno-omit-yields` in my project's cabal file didn't seem to help, but manually inserting a yield did. – paperduck Jun 25 '19 at 04:02
  • @chi Hm, `-fno-omit-yields` works when I put it in the file with OPTIONS_GHC. Not sure why it doesn't work in the cabal file. – paperduck Jun 25 '19 at 04:21
  • is it really helping if you just give the OP the *feeling* that he got help? Because `-fno-omit-yields` isn't a proper solution. The function still blocks. I think there is a problem in IT, where when someone asks a nonsensical question, we feel the urge to respond in a limited way instead of explaining things properly. Like when someone asks "how can I put a finger in a wallsocket without getting an electric shock", an IT person would answer "use isolating gloves". But that doesn't help the person to see wether the wallsocket contains current. – Michael Jun 25 '19 at 16:35

1 Answers1

3

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)
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • 3
    doesn't `watcher` block in any case? polling in an endless loop indefinitely is a way of blocking, according to my definition. – Michael Jun 24 '19 at 18:19
  • @Michael The usual definition of "blocking", in concurrent systems, is asking the OS/RTS/scheduler to remove the current thread from the list of active threads until some event happens. Busy waiting is not "blocking", and there's an extensive literature about avoiding wasting CPU time using a busy wait, which has influenced Haskell primitives. Daniel's code above runs `takeMVar` each loop, so it's blocking whenever there's nothing to do instead of polling/busy waiting. – chi Jun 25 '19 at 17:00