Newer
Older
Quasar,
newQuasar,
MonadQuasar(..),
QuasarT,
QuasarIO,
QuasarSTM,
-- ** High-level initialization
runQuasarAndExit,
runQuasarAndExitWith,
runQuasarCollectExceptions,
runQuasarCombineExceptions,
-- ** Get quasar components
quasarIOWorker,
quasarExceptionChannel,
quasarResourceManager,
askIOWorker,
askExceptionChannel,
askResourceManager,
import GHC.Records (HasField(..))
import Quasar.Resources.Disposer
import System.Exit
import Data.Bifunctor (first)
-- Invariant: the resource manager is disposed as soon as an exception is thrown to the channel
data Quasar = Quasar TIOWorker ExceptionChannel ResourceManager
instance Resource Quasar where
getDisposer (Quasar _ _ rm) = getDisposer rm
instance HasField "ioWorker" Quasar TIOWorker where
getField = quasarIOWorker
instance HasField "exceptionChannel" Quasar ExceptionChannel where
getField = quasarExceptionChannel
instance HasField "resourceManager" Quasar ResourceManager where
getField = quasarResourceManager
quasarIOWorker :: Quasar -> TIOWorker
quasarIOWorker (Quasar worker _ _) = worker
quasarExceptionChannel :: Quasar -> ExceptionChannel
quasarExceptionChannel (Quasar _ exChan _) = exChan
quasarResourceManager :: Quasar -> ResourceManager
quasarResourceManager (Quasar _ _ rm) = rm
newQuasarSTM :: TIOWorker -> ExceptionChannel -> ResourceManager -> STM Quasar
newQuasarSTM worker parentExChan parentRM = do
rm <- newUnmanagedResourceManagerSTM worker parentExChan
attachResource parentRM rm
pure $ Quasar worker (ExceptionChannel (disposeOnException rm)) rm
where
disposeOnException :: ResourceManager -> SomeException -> STM ()
disposeOnException rm ex = do
disposeEventuallySTM_ rm
throwToExceptionChannel parentExChan ex
newQuasar :: MonadQuasar m => m Quasar
newQuasar = do
worker <- askIOWorker
exChan <- askExceptionChannel
parentRM <- askResourceManager
ensureSTM $ newQuasarSTM worker exChan parentRM
--withResourceScope :: MonadQuasar m => m a -> m a
class (MonadCatch m, MonadFix m) => MonadQuasar m where
startShortIO :: ShortIO a -> m (Awaitable a)
ensureSTM :: STM a -> m a
ensureQuasarSTM :: QuasarSTM a -> m a
type QuasarT = ReaderT Quasar
type QuasarIO = QuasarT IO
newtype QuasarSTM a = QuasarSTM (ReaderT (Quasar, TVar (Awaitable ())) STM a)
deriving newtype (Functor, Applicative, Monad, MonadThrow, MonadCatch, MonadFix, Alternative)
instance (MonadIO m, MonadMask m, MonadFix m) => MonadQuasar (QuasarT m) where
ensureSTM t = liftIO (atomically t)
startShortIO fn = do
exChan <- askExceptionChannel
liftIO $ uninterruptibleMask_ $ try (runShortIO fn) >>= \case
Left ex -> do
atomically $ throwToExceptionChannel exChan ex
pure $ throwM $ toException $ AsyncException ex
Right result -> pure $ pure result
ensureQuasarSTM = quasarAtomically
instance MonadQuasar QuasarSTM where
askQuasar = QuasarSTM (asks fst)
ensureSTM fn = QuasarSTM (lift fn)
startShortIO fn = do
(quasar, effectAwaitableVar) <- QuasarSTM ask
let
worker = quasarIOWorker quasar
exChan = quasarExceptionChannel quasar
ensureSTM do
awaitable <- startShortIOSTM fn worker exChan
-- Await in reverse order, so it is almost guaranteed this only retries once
modifyTVar effectAwaitableVar (awaitSuccessOrFailure awaitable *>)
pure awaitable
ensureQuasarSTM = id
localQuasar quasar (QuasarSTM fn) = QuasarSTM (local (first (const quasar)) fn)
-- Overlappable so a QuasarT has priority over the base monad.
instance {-# OVERLAPPABLE #-} MonadQuasar m => MonadQuasar (ReaderT r m) where
askQuasar = lift askQuasar
ensureSTM t = lift (ensureSTM t)
maskIfRequired fn = do
x <- ask
lift $ maskIfRequired (runReaderT fn x)
startShortIO t = lift (startShortIO t)
ensureQuasarSTM t = lift (ensureQuasarSTM t)
localQuasar quasar fn = do
x <- ask
lift (localQuasar quasar (runReaderT fn x))
-- TODO MonadQuasar instances for StateT, WriterT, RWST, MaybeT, ...
startShortIO_ :: MonadQuasar m => ShortIO () -> m ()
startShortIO_ fn = void $ startShortIO fn
askIOWorker :: MonadQuasar m => m TIOWorker
askIOWorker = quasarIOWorker <$> askQuasar
askExceptionChannel :: MonadQuasar m => m ExceptionChannel
askExceptionChannel = quasarExceptionChannel <$> askQuasar
askResourceManager :: MonadQuasar m => m ResourceManager
askResourceManager = quasarResourceManager <$> askQuasar
liftQuasarIO :: (MonadIO m, MonadQuasar m) => QuasarIO a -> m a
liftQuasarIO fn = do
quasar <- askQuasar
liftIO $ runReaderT fn quasar
runQuasarIO :: MonadIO m => Quasar -> QuasarIO a -> m a
runQuasarIO quasar fn = liftIO $ runReaderT fn quasar
quasarAtomically :: (MonadQuasar m, MonadIO m) => QuasarSTM a -> m a
quasarAtomically (QuasarSTM fn) = do
quasar <- askQuasar
liftIO do
await =<< atomically do
effectAwaitableVar <- newTVar (pure ())
result <- runReaderT fn (quasar, effectAwaitableVar)
(result <$) <$> readTVar effectAwaitableVar
enterQuasarIO :: MonadIO m => Quasar -> QuasarIO () -> m ()
enterQuasarIO quasar fn = runQuasarIO quasar $ redirectExceptionToSink_ fn
enterQuasarSTM :: MonadQuasar m => Quasar -> QuasarSTM () -> m ()
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
enterQuasarSTM quasar fn = ensureQuasarSTM $ localQuasar quasar $ redirectExceptionToSink_ fn
redirectExceptionToSink :: MonadQuasar m => m a -> m (Maybe a)
redirectExceptionToSink fn = do
exChan <- askExceptionChannel
(Just <$> fn) `catchAll`
\ex -> ensureSTM (Nothing <$ throwToExceptionChannel exChan ex)
redirectExceptionToSink_ :: MonadQuasar m => m a -> m ()
redirectExceptionToSink_ fn = void $ redirectExceptionToSink fn
-- * Quasar initialization
withQuasarGeneric :: TIOWorker -> ExceptionChannel -> QuasarIO a -> IO a
withQuasarGeneric worker exChan fn = mask \unmask -> do
rm <- atomically $ newUnmanagedResourceManagerSTM worker exChan
let quasar = Quasar worker exChan rm
unmask (runQuasarIO quasar fn) `finally` dispose rm
-- * High-level entry helpers
runQuasarAndExit :: QuasarIO () -> IO a
runQuasarAndExit =
runQuasarAndExitWith \case
QuasarExitSuccess () -> ExitSuccess
QuasarExitAsyncException () -> ExitFailure 1
QuasarExitMainThreadFailed -> ExitFailure 1
data QuasarExitState a = QuasarExitSuccess a | QuasarExitAsyncException a | QuasarExitMainThreadFailed
runQuasarAndExitWith :: (QuasarExitState a -> ExitCode) -> QuasarIO a -> IO b
runQuasarAndExitWith exitCodeFn fn = mask \unmask -> do
worker <- newTIOWorker
(exChan, exceptionWitness) <- atomically $ newExceptionChannelWitness (loggingExceptionChannel worker)
mResult <- unmask $ withQuasarGeneric worker exChan (redirectExceptionToSink fn)
failure <- atomically exceptionWitness
exitState <- case (mResult, failure) of
(Just result, False) -> pure $ QuasarExitSuccess result
(Just result, True) -> pure $ QuasarExitAsyncException result
(Nothing, True) -> pure QuasarExitMainThreadFailed
(Nothing, False) -> do
traceIO "Invalid code path reached: Main thread failed but no asynchronous exception was witnessed. This is a bug, please report it to the `quasar`-project."
pure QuasarExitMainThreadFailed
exitWith $ exitCodeFn exitState
runQuasarCollectExceptions :: QuasarIO a -> IO (Either SomeException a, [SomeException])
runQuasarCollectExceptions fn = do
(exChan, collectExceptions) <- atomically $ newExceptionCollector panicChannel
worker <- newTIOWorker
result <- try $ withQuasarGeneric worker exChan fn
exceptions <- atomically collectExceptions
pure (result, exceptions)
runQuasarCombineExceptions :: QuasarIO a -> IO a
runQuasarCombineExceptions fn = do
(result, exceptions) <- runQuasarCollectExceptions fn
case result of
Left (ex :: SomeException) -> maybe (throwM ex) (throwM . CombinedException . (ex <|)) (nonEmpty exceptions)
Right fnResult -> maybe (pure fnResult) (throwM . CombinedException) $ nonEmpty exceptions