From 0ffa38837b6661b2f4767a57d7f70f5846203923 Mon Sep 17 00:00:00 2001 From: Jens Nolte <git@queezle.net> Date: Mon, 23 Aug 2021 18:58:13 +0200 Subject: [PATCH] Move Disposable and ResourceManager to Quasar.Disposable --- quasar.cabal | 1 - src/Quasar/Async.hs | 2 +- src/Quasar/Core.hs | 162 --------------------------------------- src/Quasar/Disposable.hs | 153 +++++++++++++++++++++++++++++++++++- src/Quasar/Observable.hs | 2 +- 5 files changed, 154 insertions(+), 166 deletions(-) delete mode 100644 src/Quasar/Core.hs diff --git a/quasar.cabal b/quasar.cabal index 20b4009..16f256a 100644 --- a/quasar.cabal +++ b/quasar.cabal @@ -80,7 +80,6 @@ library exposed-modules: Quasar.Async Quasar.Awaitable - Quasar.Core Quasar.Disposable Quasar.Observable Quasar.Observable.Delta diff --git a/src/Quasar/Async.hs b/src/Quasar/Async.hs index a28801f..a901fd7 100644 --- a/src/Quasar/Async.hs +++ b/src/Quasar/Async.hs @@ -32,7 +32,7 @@ import Control.Monad.Catch import Control.Monad.Reader import Data.HashSet import Quasar.Awaitable -import Quasar.Core +import Quasar.Disposable import Quasar.Prelude diff --git a/src/Quasar/Core.hs b/src/Quasar/Core.hs deleted file mode 100644 index 4faae87..0000000 --- a/src/Quasar/Core.hs +++ /dev/null @@ -1,162 +0,0 @@ -module Quasar.Core ( - -- * Disposable - IsDisposable(..), - Disposable, - disposeIO, - newDisposable, - synchronousDisposable, - noDisposable, - - -- ** ResourceManager - ResourceManager, - HasResourceManager(..), - newResourceManager, - disposeEventually, - attachDisposable, - attachDisposeAction, - attachDisposeAction_, -) where - -import Control.Concurrent.STM -import Control.Monad.Catch -import Control.Monad.Reader -import Quasar.Awaitable -import Quasar.Prelude - - --- * Disposable - -class IsDisposable a where - -- TODO document laws: must not throw exceptions, is idempotent - - -- | Dispose a resource. - dispose :: a -> IO (Awaitable ()) - dispose = dispose . toDisposable - - isDisposed :: a -> Awaitable () - isDisposed = isDisposed . toDisposable - - toDisposable :: a -> Disposable - toDisposable = Disposable - - {-# MINIMAL toDisposable | (dispose, isDisposed) #-} - --- | Dispose a resource in the IO monad. -disposeIO :: IsDisposable a => a -> IO () -disposeIO = awaitIO <=< dispose - -instance IsDisposable a => IsDisposable (Maybe a) where - toDisposable = maybe noDisposable toDisposable - - - -data Disposable = forall a. IsDisposable a => Disposable a - -instance IsDisposable Disposable where - dispose (Disposable x) = dispose x - toDisposable = id - -instance Semigroup Disposable where - x <> y = toDisposable $ CombinedDisposable x y - -instance Monoid Disposable where - mempty = toDisposable EmptyDisposable - mconcat = toDisposable . ListDisposable - -instance IsAwaitable () Disposable where - toAwaitable = isDisposed - - -newtype FnDisposable = FnDisposable (TMVar (Either (IO (Awaitable ())) (Awaitable ()))) - -instance IsDisposable FnDisposable where - dispose (FnDisposable var) = - bracketOnError - do atomically $ takeTMVar var - do atomically . putTMVar var - \case - Left action -> do - awaitable <- action - atomically $ putTMVar var $ Right awaitable - pure awaitable - Right awaitable -> pure awaitable - - isDisposed = toAwaitable - -instance IsAwaitable () FnDisposable where - runAwaitable :: (MonadQuerySTM m) => FnDisposable -> m (Either SomeException ()) - runAwaitable (FnDisposable var) = do - -- Query if dispose has started - awaitable <- querySTM $ join . fmap rightToMaybe <$> tryReadTMVar var - -- Query if dispose is completed - runAwaitable awaitable - - - -data CombinedDisposable = CombinedDisposable Disposable Disposable - -instance IsDisposable CombinedDisposable where - dispose (CombinedDisposable x y) = liftA2 (<>) (dispose x) (dispose y) - isDisposed (CombinedDisposable x y) = liftA2 (<>) (isDisposed x) (isDisposed y) - -newtype ListDisposable = ListDisposable [Disposable] - -instance IsDisposable ListDisposable where - dispose (ListDisposable disposables) = mconcat <$> traverse dispose disposables - isDisposed (ListDisposable disposables) = traverse_ isDisposed disposables - - - -data EmptyDisposable = EmptyDisposable - -instance IsDisposable EmptyDisposable where - dispose _ = pure $ pure () - isDisposed _ = successfulAwaitable () - - - -newDisposable :: MonadIO m => IO (Awaitable ()) -> m Disposable -newDisposable = liftIO . fmap (toDisposable . FnDisposable) . newTMVarIO . Left - -synchronousDisposable :: IO () -> IO Disposable -synchronousDisposable = newDisposable . fmap pure . liftIO - -noDisposable :: Disposable -noDisposable = mempty - - -data ResourceManager = ResourceManager - -class HasResourceManager a where - getResourceManager :: a -> ResourceManager - -instance IsDisposable ResourceManager where - toDisposable = undefined - -newResourceManager :: IO ResourceManager -newResourceManager = pure ResourceManager - --- | Start disposing a resource but instead of waiting for the operation to complete, pass the responsibility to a `ResourceManager`. --- --- The synchronous part of the `dispose`-Function will be run immediately but the resulting `Awaitable` will be passed to the resource manager. -disposeEventually :: (IsDisposable a, MonadIO m) => ResourceManager -> a -> m () -disposeEventually _resourceManager disposable = liftIO $ do - disposeCompleted <- dispose disposable - peekAwaitable disposeCompleted >>= \case - Just (Left ex) -> throwIO ex - Just (Right ()) -> pure () - Nothing -> undefined -- TODO register on resourceManager - -attachDisposable :: (IsDisposable a, MonadIO m) => ResourceManager -> a -> m () -attachDisposable _resourceManager disposable = liftIO undefined - --- | Creates an `Disposable` that is bound to a ResourceManager. It will automatically be disposed when the resource manager is disposed. -attachDisposeAction :: MonadIO m => ResourceManager -> IO (Awaitable ()) -> m Disposable -attachDisposeAction resourceManager action = do - disposable <- newDisposable action - attachDisposable resourceManager disposable - pure disposable - --- | Attaches a dispose action to a ResourceManager. It will automatically be run when the resource manager is disposed. -attachDisposeAction_ :: MonadIO m => ResourceManager -> IO (Awaitable ()) -> m () -attachDisposeAction_ resourceManager action = void $ attachDisposeAction resourceManager action diff --git a/src/Quasar/Disposable.hs b/src/Quasar/Disposable.hs index a6facd1..25c7680 100644 --- a/src/Quasar/Disposable.hs +++ b/src/Quasar/Disposable.hs @@ -1,11 +1,162 @@ module Quasar.Disposable ( + -- * Disposable IsDisposable(..), Disposable, disposeIO, newDisposable, synchronousDisposable, noDisposable, + + -- ** ResourceManager + ResourceManager, + HasResourceManager(..), + newResourceManager, + disposeEventually, + attachDisposable, + attachDisposeAction, + attachDisposeAction_, ) where -import Quasar.Core +import Control.Concurrent.STM +import Control.Monad.Catch +import Control.Monad.Reader +import Quasar.Awaitable +import Quasar.Prelude + + +-- * Disposable + +class IsDisposable a where + -- TODO document laws: must not throw exceptions, is idempotent + + -- | Dispose a resource. + dispose :: a -> IO (Awaitable ()) + dispose = dispose . toDisposable + + isDisposed :: a -> Awaitable () + isDisposed = isDisposed . toDisposable + + toDisposable :: a -> Disposable + toDisposable = Disposable + + {-# MINIMAL toDisposable | (dispose, isDisposed) #-} + +-- | Dispose a resource in the IO monad. +disposeIO :: IsDisposable a => a -> IO () +disposeIO = awaitIO <=< dispose + +instance IsDisposable a => IsDisposable (Maybe a) where + toDisposable = maybe noDisposable toDisposable + + + +data Disposable = forall a. IsDisposable a => Disposable a + +instance IsDisposable Disposable where + dispose (Disposable x) = dispose x + toDisposable = id + +instance Semigroup Disposable where + x <> y = toDisposable $ CombinedDisposable x y + +instance Monoid Disposable where + mempty = toDisposable EmptyDisposable + mconcat = toDisposable . ListDisposable + +instance IsAwaitable () Disposable where + toAwaitable = isDisposed + + +newtype FnDisposable = FnDisposable (TMVar (Either (IO (Awaitable ())) (Awaitable ()))) + +instance IsDisposable FnDisposable where + dispose (FnDisposable var) = + bracketOnError + do atomically $ takeTMVar var + do atomically . putTMVar var + \case + Left action -> do + awaitable <- action + atomically $ putTMVar var $ Right awaitable + pure awaitable + Right awaitable -> pure awaitable + + isDisposed = toAwaitable + +instance IsAwaitable () FnDisposable where + runAwaitable :: (MonadQuerySTM m) => FnDisposable -> m (Either SomeException ()) + runAwaitable (FnDisposable var) = do + -- Query if dispose has started + awaitable <- querySTM $ join . fmap rightToMaybe <$> tryReadTMVar var + -- Query if dispose is completed + runAwaitable awaitable + + + +data CombinedDisposable = CombinedDisposable Disposable Disposable + +instance IsDisposable CombinedDisposable where + dispose (CombinedDisposable x y) = liftA2 (<>) (dispose x) (dispose y) + isDisposed (CombinedDisposable x y) = liftA2 (<>) (isDisposed x) (isDisposed y) + +newtype ListDisposable = ListDisposable [Disposable] + +instance IsDisposable ListDisposable where + dispose (ListDisposable disposables) = mconcat <$> traverse dispose disposables + isDisposed (ListDisposable disposables) = traverse_ isDisposed disposables + + + +data EmptyDisposable = EmptyDisposable + +instance IsDisposable EmptyDisposable where + dispose _ = pure $ pure () + isDisposed _ = successfulAwaitable () + + + +newDisposable :: MonadIO m => IO (Awaitable ()) -> m Disposable +newDisposable = liftIO . fmap (toDisposable . FnDisposable) . newTMVarIO . Left + +synchronousDisposable :: IO () -> IO Disposable +synchronousDisposable = newDisposable . fmap pure . liftIO + +noDisposable :: Disposable +noDisposable = mempty + + +data ResourceManager = ResourceManager + +class HasResourceManager a where + getResourceManager :: a -> ResourceManager + +instance IsDisposable ResourceManager where + toDisposable = undefined + +newResourceManager :: IO ResourceManager +newResourceManager = pure ResourceManager + +-- | Start disposing a resource but instead of waiting for the operation to complete, pass the responsibility to a `ResourceManager`. +-- +-- The synchronous part of the `dispose`-Function will be run immediately but the resulting `Awaitable` will be passed to the resource manager. +disposeEventually :: (IsDisposable a, MonadIO m) => ResourceManager -> a -> m () +disposeEventually _resourceManager disposable = liftIO $ do + disposeCompleted <- dispose disposable + peekAwaitable disposeCompleted >>= \case + Just (Left ex) -> throwIO ex + Just (Right ()) -> pure () + Nothing -> undefined -- TODO register on resourceManager + +attachDisposable :: (IsDisposable a, MonadIO m) => ResourceManager -> a -> m () +attachDisposable _resourceManager disposable = liftIO undefined + +-- | Creates an `Disposable` that is bound to a ResourceManager. It will automatically be disposed when the resource manager is disposed. +attachDisposeAction :: MonadIO m => ResourceManager -> IO (Awaitable ()) -> m Disposable +attachDisposeAction resourceManager action = do + disposable <- newDisposable action + attachDisposable resourceManager disposable + pure disposable +-- | Attaches a dispose action to a ResourceManager. It will automatically be run when the resource manager is disposed. +attachDisposeAction_ :: MonadIO m => ResourceManager -> IO (Awaitable ()) -> m () +attachDisposeAction_ resourceManager action = void $ attachDisposeAction resourceManager action diff --git a/src/Quasar/Observable.hs b/src/Quasar/Observable.hs index 1da3736..e09b075 100644 --- a/src/Quasar/Observable.hs +++ b/src/Quasar/Observable.hs @@ -40,7 +40,7 @@ import Data.HashMap.Strict qualified as HM import Data.Unique import Quasar.Async import Quasar.Awaitable -import Quasar.Core +import Quasar.Disposable import Quasar.Prelude -- GitLab