mkAutoUpdateHelper :: UpdateSettings a -> Maybe (a -> IO a) -> IO (IO a)
mkAutoUpdateHelper us updateActionModify = do
    -- A baton to tell the worker thread to generate a new value.
    needsRunning <- newEmptyMVar

    -- The initial response variable. Response variables allow the requesting
    -- thread to block until a value is generated by the worker thread.
    responseVar0 <- newEmptyMVar

    -- The current value, if available. We start off with a Left value
    -- indicating no value is available, and the above-created responseVar0 to
    -- give a variable to block on.
    currRef <- newIORef $ Left responseVar0

    -- This is used to set a value in the currRef variable when the worker
    -- thread exits. In reality, that value should never be used, since the
    -- worker thread exiting only occurs if an async exception is thrown, which
    -- should only occur if there are no references to needsRunning left.
    -- However, this handler will make error messages much clearer if there's a
    -- bug in the implementation.
    let fillRefOnExit f = do
            eres <- try f
            case eres of
                Left e -> writeIORef currRef $ error $
                    "Control.AutoUpdate.mkAutoUpdate: worker thread exited with exception: "
                    ++ show (e :: SomeException)
                Right () -> writeIORef currRef $ error $
                    "Control.AutoUpdate.mkAutoUpdate: worker thread exited normally, "
                    ++ "which should be impossible due to usage of infinite loop"

    -- fork the worker thread immediately. Note that we mask async exceptions,
    -- but *not* in an uninterruptible manner. This will allow a
    -- BlockedIndefinitelyOnMVar exception to still be thrown, which will take
    -- down this thread when all references to the returned function are
    -- garbage collected, and therefore there is no thread that can fill the
    -- needsRunning MVar.
    --
    -- Note that since we throw away the ThreadId of this new thread and never
    -- calls myThreadId, normal async exceptions can never be thrown to it,
    -- only RTS exceptions.
    mask_ $ void $ forkIO $ fillRefOnExit $ do
        -- This infinite loop makes up out worker thread. It takes an a
        -- responseVar value where the next value should be putMVar'ed to for
        -- the benefit of any requesters currently blocked on it.
        let loop responseVar maybea = do
                -- block until a value is actually needed
                takeMVar needsRunning

                -- new value requested, so run the updateAction
                a <- catchSome $ maybe (updateAction us) id (updateActionModify <*> maybea)

                -- we got a new value, update currRef and lastValue
                writeIORef currRef $ Right a
                putMVar responseVar a

                -- delay until we're needed again
                threadDelay $ updateFreq us

                -- delay's over. create a new response variable and set currRef
                -- to use it, so that the next requester will block on that
                -- variable. Then loop again with the updated response
                -- variable.
                responseVar' <- newEmptyMVar
                writeIORef currRef $ Left responseVar'
                loop responseVar' (Just a)

        -- Kick off the loop, with the initial responseVar0 variable.
        loop responseVar0 Nothing

    return $ do
        mval <- readIORef currRef
        case mval of
            Left responseVar -> do
                -- no current value, force the worker thread to run...
                void $ tryPutMVar needsRunning ()

                -- and block for the result from the worker
                readMVar responseVar
            -- we have a current value, use it
            Right val -> return val

-- | Turn a runtime exception into an impure exception, so that all 'IO'
-- actions will complete successfully. This simply defers the exception until
-- the value is forced.
mkAutoUpdateHelper :: UpdateSettings a -> Maybe (a -> IO a) -> IO (IO a)
mkAutoUpdateHelper us updateActionModify = do
    -- A baton to tell the worker thread to generate a new value.
    needsRunning <- newEmptyMVar

    -- The initial response variable. Response variables allow the requesting
    -- thread to block until a value is generated by the worker thread.
    responseVar0 <- newEmptyMVar

    -- The current value, if available. We start off with a Left value
    -- indicating no value is available, and the above-created responseVar0 to
    -- give a variable to block on.
    currRef <- newIORef $ Left responseVar0

    -- This is used to set a value in the currRef variable when the worker
    -- thread exits. In reality, that value should never be used, since the
    -- worker thread exiting only occurs if an async exception is thrown, which
    -- should only occur if there are no references to needsRunning left.
    -- However, this handler will make error messages much clearer if there's a
    -- bug in the implementation.
    let fillRefOnExit f = do
            eres <- try f
            case eres of
                Left e -> writeIORef currRef $ error $
                    "Control.AutoUpdate.mkAutoUpdate: worker thread exited with exception: "
                    ++ show (e :: SomeException)
                Right () -> writeIORef currRef $ error $
                    "Control.AutoUpdate.mkAutoUpdate: worker thread exited normally, "
                    ++ "which should be impossible due to usage of infinite loop"

    -- fork the worker thread immediately. Note that we mask async exceptions,
    -- but *not* in an uninterruptible manner. This will allow a
    -- BlockedIndefinitelyOnMVar exception to still be thrown, which will take
    -- down this thread when all references to the returned function are
    -- garbage collected, and therefore there is no thread that can fill the
    -- needsRunning MVar.
    --
    -- Note that since we throw away the ThreadId of this new thread and never
    -- calls myThreadId, normal async exceptions can never be thrown to it,
    -- only RTS exceptions.
    mask_ $ void $ forkIO $ fillRefOnExit $ do
        -- This infinite loop makes up out worker thread. It takes an a
        -- responseVar value where the next value should be putMVar'ed to for
        -- the benefit of any requesters currently blocked on it.
        let loop responseVar maybea = do
                -- block until a value is actually needed
                takeMVar needsRunning

                -- new value requested, so run the updateAction
                a <- catchSome $ maybe (updateAction us) id (updateActionModify <*> maybea)

                -- we got a new value, update currRef and lastValue
                writeIORef currRef $ Right a
                putMVar responseVar a

                -- delay until we're needed again
                threadDelay $ updateFreq us

                -- delay's over. create a new response variable and set currRef
                -- to use it, so that the next requester will block on that
                -- variable. Then loop again with the updated response
                -- variable.
                responseVar' <- newEmptyMVar
                writeIORef currRef $ Left responseVar'
                loop responseVar' (Just a)

        -- Kick off the loop, with the initial responseVar0 variable.
        loop responseVar0 Nothing

    return $ do
        mval <- readIORef currRef
        case mval of
            Left responseVar -> do
                -- no current value, force the worker thread to run...
                void $ tryPutMVar needsRunning ()

                -- and block for the result from the worker
                readMVar responseVar
            -- we have a current value, use it
            Right val -> return val

-- | Turn a runtime exception into an impure exception, so that all 'IO'
-- actions will complete successfully. This simply defers the exception until
-- the value is forced.
mkAutoUpdateHelper us updateActionModify = do
    -- A baton to tell the worker thread to generate a new value.
    needsRunning <- newEmptyMVar

    -- The initial response variable. Response variables allow the requesting
    -- thread to block until a value is generated by the worker thread.
    responseVar0 <- newEmptyMVar

    -- The current value, if available. We start off with a Left value
    -- indicating no value is available, and the above-created responseVar0 to
    -- give a variable to block on.
    currRef <- newIORef $ Left responseVar0

    -- This is used to set a value in the currRef variable when the worker
    -- thread exits. In reality, that value should never be used, since the
    -- worker thread exiting only occurs if an async exception is thrown, which
    -- should only occur if there are no references to needsRunning left.
    -- However, this handler will make error messages much clearer if there's a
    -- bug in the implementation.
    let fillRefOnExit f = do
            eres <- try f
            case eres of
                Left e -> writeIORef currRef $ error $
                    "Control.AutoUpdate.mkAutoUpdate: worker thread exited with exception: "
                    ++ show (e :: SomeException)
                Right () -> writeIORef currRef $ error $
                    "Control.AutoUpdate.mkAutoUpdate: worker thread exited normally, "
                    ++ "which should be impossible due to usage of infinite loop"

    -- fork the worker thread immediately. Note that we mask async exceptions,
    -- but *not* in an uninterruptible manner. This will allow a
    -- BlockedIndefinitelyOnMVar exception to still be thrown, which will take
    -- down this thread when all references to the returned function are
    -- garbage collected, and therefore there is no thread that can fill the
    -- needsRunning MVar.
    --
    -- Note that since we throw away the ThreadId of this new thread and never
    -- calls myThreadId, normal async exceptions can never be thrown to it,
    -- only RTS exceptions.
    mask_ $ void $ forkIO $ fillRefOnExit $ do
        -- This infinite loop makes up out worker thread. It takes an a
        -- responseVar value where the next value should be putMVar'ed to for
        -- the benefit of any requesters currently blocked on it.
        let loop responseVar maybea = do
                -- block until a value is actually needed
                takeMVar needsRunning

                -- new value requested, so run the updateAction
                a <- catchSome $ maybe (updateAction us) id (updateActionModify <*> maybea)

                -- we got a new value, update currRef and lastValue
                writeIORef currRef $ Right a
                putMVar responseVar a

                -- delay until we're needed again
                threadDelay $ updateFreq us

                -- delay's over. create a new response variable and set currRef
                -- to use it, so that the next requester will block on that
                -- variable. Then loop again with the updated response
                -- variable.
                responseVar' <- newEmptyMVar
                writeIORef currRef $ Left responseVar'
                loop responseVar' (Just a)

        -- Kick off the loop, with the initial responseVar0 variable.
        loop responseVar0 Nothing

    return $ do
        mval <- readIORef currRef
        case mval of
            Left responseVar -> do
                -- no current value, force the worker thread to run...
                void $ tryPutMVar needsRunning ()

                -- and block for the result from the worker
                readMVar responseVar
            -- we have a current value, use it
            Right val -> return val

-- | Turn a runtime exception into an impure exception, so that all 'IO'
-- actions will complete successfully. This simply defers the exception until
-- the value is forced.