Search code examples
haskellenumeratorloops

How to create an ever-retrying Enumerator


I'm using John Millikin's enumerator package and am trying to create something roughly equivalent to Data.Enumerator.Binary.enumHandle, except it connects the socket itself, then tries to enumerate the resulting handle. The difficulty comes from the fact that the connection is unreliable, and I'd like it to reconnect and resume enumerating if something goes wrong.

I'd normally expect Enumerator to be its own meaningful Monad instance, but since it's a type alias for a function, monadic behavior on it is just a reader of its input step, which doesn't appear to be much use here. I tried to throw something together that just kept looping the Enumerator, using catchError, but it didn't do what I expected and I couldn't figure out what it was doing, so I was wondering if anyone could suggest a nice idiomatic approach to this. I'm fine with just a skeleton of a solution, since there are obviously many details that I've omitted.

Any ideas?


Solution

  • You probably have to write that yourself. I don't think it's predefined anywhere. However, it's not that difficult:

    enumConnectAgain :: forall b m. MonadIO m => IO Handle -> Enumerator ByteString m b
    enumConnectAgain open step =
        fix $ \again -> do
            mh <- liftIO (Ex.try open)
            case mh of
              Left (ex :: SomeException) -> again
              Right h                    -> loop h step
    
        where
        loop :: Handle -> Step ByteString m b -> Iteratee ByteString m b
        loop h step@(Continue k) = do
            mstr <- liftIO (Ex.try $ B.hGetSome h 1024)
            case mstr of
              Left (ex :: SomeException) -> enumConnectAgain open step
              Right str                  -> k (Chunks [str]) >>== loop h
        loop h step = returnI step
    

    That should work.