module Freckle.App.Async ( async , immortalCreate , immortalCreateLogged ) where import Freckle.App.Prelude import Blammo.Logging import qualified Control.Immortal as Immortal import Control.Monad (forever) import qualified Data.Aeson.Compat as KeyMap import UnliftIO.Async (Async) import qualified UnliftIO.Async as UnliftIO import UnliftIO.Concurrent (threadDelay) import UnliftIO.Exception (SomeException, displayException) -- | 'UnliftIO.Async.async' but passing the thread context along async :: (MonadMask m, MonadUnliftIO m) => m a -> m (Async a) async f = do tc <- liftIO $ KeyMap.toList <$> myThreadContext UnliftIO.async $ withThreadContext tc f -- | Wrapper around creating "Control.Immortal" processes -- -- Features: -- -- - Ensures the thread context is correctly passed to both your spawned action -- and your error handler -- - Blocks forever after spawning your thread. immortalCreate :: (MonadMask m, MonadUnliftIO m) => (Either SomeException () -> m ()) -- ^ How to handle unexpected finish -> m () -- ^ The action to run persistently -> m a immortalCreate onUnexpected act = do tc <- liftIO $ KeyMap.toList <$> myThreadContext let act' = withThreadContext tc act onUnexpected' = withThreadContext tc . onUnexpected void $ Immortal.create $ \thread -> do Immortal.onUnexpectedFinish thread onUnexpected' act' forever $ threadDelay maxBound -- | 'immortalCreate' with logging of unexpected finishes immortalCreateLogged :: (MonadMask m, MonadUnliftIO m, MonadLogger m) => m () -> m a immortalCreateLogged = immortalCreate $ either logEx pure where logEx ex = logError $ "Unexpected Finish" :# ["exception" .= displayException ex]