{-| Module : Control.Immortal.Worker Description : Immortal thread with logging and restart on exceptions. Copyright : (c) Anton Gushcha (ncrashed), 2020 License : MIT Maintainer : ncrashed@gmail.com Stability : experimental Portability : Portable Here is a longer description of this module, containing some commentary with @some markup@. Typical usage: @ worker "supervisor" $ const $ forever $ do logInfoN "Supervisor started" let subworkers = [ subworker1 , subworker2 ] traverse_ (isolate_ "subworker") subworkers liftIO $ threadDelay 10_000_000 @ -} module Control.Immortal.Worker( workerWith , worker , isolate , isolate_ ) where import Control.Concurrent (threadDelay) import Control.DeepSeq import Control.Exception.Safe (SomeException, catchDeep) import Control.Monad.IO.Unlift import Control.Monad.Logger import Data.Text (pack) import qualified Control.Immortal as I -- | Start immortal worker that logs on exceptions and restarts. -- -- Note that action is not looped implicitly. Add 'Control.Monad.forever' into action -- manually to achive this. workerWith :: (MonadUnliftIO m, MonadLogger m) => (String -> SomeException -> m ()) -- ^ Action to perform before worker restart -> String -- ^ Worker label for thred -> (I.Thread -> m ()) -- ^ Worker action (no looping is added) -> m I.Thread workerWith logthem lbl f = I.createWithLabel lbl $ \thread -> I.onUnexpectedFinish thread (either (logthem lbl) (const $ pure ())) (f thread) -- | Helper that starts new immortal thread with logging of errors worker :: (MonadUnliftIO m, MonadLogger m) => String -> (I.Thread -> m ()) -> m I.Thread worker = workerWith $ \lbl e -> do logErrorN $ "Worker " <> pack lbl <> " exit with: " <> (pack . show) e liftIO $ threadDelay 1000000 -- | If computation fails, print log and return default value. isolate :: (MonadUnliftIO m, MonadLogger m, NFData a) => String -> a -> m a -> m a isolate title a0 ma = do run <- askRunInIO liftIO $ catchDeep (run ma) $ \(e :: SomeException) -> run $ do logErrorN $ "Isolated action " <> pack title <> " failed: " <> (pack . show) e pure a0 -- | Same as `isolate` but returns empty tuple isolate_ :: (MonadUnliftIO m, MonadLogger m) => String -> m () -> m () isolate_ lbl = isolate lbl ()