From 9cdd243b9c49cc636d0d7056ac01abc5e3c5701a Mon Sep 17 00:00:00 2001
From: Jens Nolte <git@queezle.net>
Date: Mon, 10 Oct 2022 00:20:11 +0200
Subject: [PATCH] Add TSimpleDisposer

Co-authored-by: Jan Beinke <git@janbeinke.com>
---
 quasar/src/Quasar/Resources.hs          | 26 +++++++
 quasar/src/Quasar/Resources/Disposer.hs | 92 ++++++++++++++++++++-----
 2 files changed, 101 insertions(+), 17 deletions(-)

diff --git a/quasar/src/Quasar/Resources.hs b/quasar/src/Quasar/Resources.hs
index b6d43ca..f391261 100644
--- a/quasar/src/Quasar/Resources.hs
+++ b/quasar/src/Quasar/Resources.hs
@@ -14,6 +14,10 @@ module Quasar.Resources (
   registerDisposeTransaction_,
   registerDisposeTransactionIO,
   registerDisposeTransactionIO_,
+  registerSimpleDisposeTransaction,
+  registerSimpleDisposeTransaction_,
+  registerSimpleDisposeTransactionIO,
+  registerSimpleDisposeTransactionIO_,
   disposeEventually,
   disposeEventually_,
   disposeEventuallyIO,
@@ -31,15 +35,19 @@ module Quasar.Resources (
   -- ** Disposer
   Disposer,
   TDisposer,
+  TSimpleDisposer,
   disposeTDisposer,
+  disposeTSimpleDisposer,
   newUnmanagedIODisposer,
   newUnmanagedSTMDisposer,
+  newUnmanagedTSimpleDisposer,
   trivialDisposer,
 
   -- ** Resource manager
   ResourceManager,
   newUnmanagedResourceManagerSTM,
   attachResource,
+  tryAttachResource,
 ) where
 
 
@@ -101,6 +109,24 @@ registerDisposeTransactionIO fn = quasarAtomically $ registerDisposeTransaction
 registerDisposeTransactionIO_ :: (MonadQuasar m, MonadIO m) => STM () -> m ()
 registerDisposeTransactionIO_ fn = quasarAtomically $ void $ registerDisposeTransaction fn
 
+registerSimpleDisposeTransaction :: (MonadQuasar m, MonadSTM' r CanThrow m) => STM' NoRetry NoThrow () -> m TSimpleDisposer
+registerSimpleDisposeTransaction fn = do
+  rm <- askResourceManager
+  liftSTM' do
+    disposer <- newUnmanagedTSimpleDisposer fn
+    attachResource rm disposer
+    pure disposer
+{-# SPECIALIZE registerSimpleDisposeTransaction :: STM' NoRetry NoThrow () -> QuasarSTM TSimpleDisposer #-}
+
+registerSimpleDisposeTransaction_ :: (MonadQuasar m, MonadSTM' r CanThrow m) => STM' NoRetry NoThrow () -> m ()
+registerSimpleDisposeTransaction_ fn = liftQuasarSTM' $ void $ registerSimpleDisposeTransaction fn
+
+registerSimpleDisposeTransactionIO :: (MonadQuasar m, MonadIO m) => STM' NoRetry NoThrow () -> m TSimpleDisposer
+registerSimpleDisposeTransactionIO fn = quasarAtomically $ registerSimpleDisposeTransaction fn
+
+registerSimpleDisposeTransactionIO_ :: (MonadQuasar m, MonadIO m) => STM' NoRetry NoThrow () -> m ()
+registerSimpleDisposeTransactionIO_ fn = quasarAtomically $ void $ registerSimpleDisposeTransaction fn
+
 registerNewResource :: forall a m. (Resource a, MonadQuasar m, MonadIO m, MonadMask m) => m a -> m a
 registerNewResource fn = do
   rm <- askResourceManager
diff --git a/quasar/src/Quasar/Resources/Disposer.hs b/quasar/src/Quasar/Resources/Disposer.hs
index d2e58ba..6b1a0d5 100644
--- a/quasar/src/Quasar/Resources/Disposer.hs
+++ b/quasar/src/Quasar/Resources/Disposer.hs
@@ -14,10 +14,15 @@ module Quasar.Resources.Disposer (
   TDisposer,
   disposeTDisposer,
 
+  TSimpleDisposer,
+  newUnmanagedTSimpleDisposer,
+  disposeTSimpleDisposer,
+
   -- * Resource manager
   ResourceManager,
   newUnmanagedResourceManagerSTM,
   attachResource,
+  tryAttachResource,
 ) where
 
 
@@ -67,6 +72,7 @@ type DisposerState = TOnce DisposeFn (Future ())
 data DisposerElement
   = IODisposer Unique TIOWorker ExceptionSink DisposerState Finalizers
   | STMDisposer TDisposerElement
+  | STMSimpleDisposer TSimpleDisposerElement
   | ResourceManagerDisposer ResourceManager
 
 instance Resource DisposerElement where
@@ -74,10 +80,12 @@ instance Resource DisposerElement where
 
   isDisposed (IODisposer _ _ _ state _) = join (toFuture state)
   isDisposed (STMDisposer tdisposer) = isDisposed tdisposer
+  isDisposed (STMSimpleDisposer tdisposer) = isDisposed tdisposer
   isDisposed (ResourceManagerDisposer resourceManager) = resourceManagerIsDisposed resourceManager
 
   isDisposing (IODisposer _ _ _ state _) = void (toFuture state)
   isDisposing (STMDisposer tdisposer) = isDisposing tdisposer
+  isDisposing (STMSimpleDisposer tdisposer) = isDisposing tdisposer
   isDisposing (ResourceManagerDisposer resourceManager) = resourceManagerIsDisposing resourceManager
 
 
@@ -88,12 +96,6 @@ type STMDisposerState = TOnce (STM ()) (Future ())
 
 data TDisposerElement = TDisposerElement Unique TIOWorker ExceptionSink STMDisposerState Finalizers
 
-newUnmanagedSTMDisposer :: MonadSTM' r t m => STM () -> TIOWorker -> ExceptionSink -> m TDisposer
-newUnmanagedSTMDisposer fn worker sink = do
-  key <- newUniqueSTM
-  element <-  TDisposerElement key worker sink <$> newTOnce fn <*> newFinalizers
-  pure $ TDisposer [element]
-
 instance Resource TDisposerElement where
   toDisposer disposer = Disposer [STMDisposer disposer]
   isDisposed (TDisposerElement _ _ _ state _) = join (toFuture state)
@@ -104,6 +106,12 @@ instance Resource [TDisposerElement] where
   isDisposed tds = isDisposed (toDisposer tds)
   isDisposing tds = isDisposing (toDisposer tds)
 
+newUnmanagedSTMDisposer :: MonadSTM' r t m => STM () -> TIOWorker -> ExceptionSink -> m TDisposer
+newUnmanagedSTMDisposer fn worker sink = do
+  key <- newUniqueSTM
+  element <-  TDisposerElement key worker sink <$> newTOnce fn <*> newFinalizers
+  pure $ TDisposer [element]
+
 disposeTDisposer :: MonadSTM m => TDisposer -> m ()
 disposeTDisposer (TDisposer elements) = liftSTM $ mapM_ go elements
   where
@@ -116,7 +124,7 @@ disposeTDisposer (TDisposer elements) = liftSTM $ mapM_ go elements
         startDisposeFn :: STM () -> STM (Future ())
         startDisposeFn disposeFn = do
           disposeFn `catchAll` throwToExceptionSink sink
-          runFinalizers finalizers
+          liftSTM' $ runFinalizers finalizers
           pure $ pure ()
 
 beginDisposeSTMDisposer :: MonadSTM' r t m => TDisposerElement -> m (Future ())
@@ -149,6 +157,46 @@ beginDisposeSTMDisposer (TDisposerElement _ worker sink state finalizers) = lift
 
 
 
+type STMSimpleDisposerState = TOnce (STM' NoRetry NoThrow ()) ()
+
+data TSimpleDisposerElement = TSimpleDisposerElement Unique STMSimpleDisposerState Finalizers
+
+newtype TSimpleDisposer = TSimpleDisposer [TSimpleDisposerElement]
+  deriving newtype (Semigroup, Monoid)
+
+instance Resource TSimpleDisposer where
+  toDisposer (TSimpleDisposer ds) = toDisposer ds
+
+instance Resource TSimpleDisposerElement where
+  toDisposer disposer = Disposer [STMSimpleDisposer disposer]
+  isDisposed (TSimpleDisposerElement _ state _) = toFuture state
+  isDisposing = isDisposed
+
+instance Resource [TSimpleDisposerElement] where
+  toDisposer tds = Disposer (STMSimpleDisposer <$> tds)
+  isDisposed tds = isDisposed (toDisposer tds)
+  isDisposing tds = isDisposing (toDisposer tds)
+
+newUnmanagedTSimpleDisposer :: MonadSTM' r t m => STM' NoRetry NoThrow () -> m TSimpleDisposer
+newUnmanagedTSimpleDisposer fn = do
+  key <- newUniqueSTM
+  element <-  TSimpleDisposerElement key <$> newTOnce fn <*> newFinalizers
+  pure $ TSimpleDisposer [element]
+
+disposeTSimpleDisposer :: MonadSTM' r t m => TSimpleDisposer -> m ()
+disposeTSimpleDisposer (TSimpleDisposer elements) = liftSTM' do
+  mapM_ disposeTSimpleDisposerElement elements
+
+disposeTSimpleDisposerElement :: TSimpleDisposerElement -> STM' r t ()
+disposeTSimpleDisposerElement (TSimpleDisposerElement _ state finalizers) =
+  mapFinalizeTOnce state startDisposeFn
+  where
+    startDisposeFn :: STM' NoRetry NoThrow () -> STM' r t ()
+    startDisposeFn disposeFn = do
+      noRetry $ noThrow disposeFn
+      runFinalizers finalizers
+
+
 -- | A trivial disposer that does not perform any action when disposed.
 trivialDisposer :: Disposer
 trivialDisposer = mempty
@@ -175,6 +223,7 @@ disposeEventually (toDisposer -> Disposer ds) = liftSTM' do
     f (IODisposer _ worker exChan state finalizers) =
       beginDisposeFnDisposer worker exChan state finalizers
     f (STMDisposer disposer) = beginDisposeSTMDisposer disposer
+    f (STMSimpleDisposer disposer) = pure () <$ disposeTSimpleDisposerElement disposer
     f (ResourceManagerDisposer resourceManager) =
       beginDisposeResourceManager resourceManager
 
@@ -210,12 +259,14 @@ beginDisposeFnDisposer worker exChan disposeState finalizers = liftSTM' do
 disposerKey :: DisposerElement -> Unique
 disposerKey (IODisposer key _ _ _ _) = key
 disposerKey (STMDisposer (TDisposerElement key _ _ _ _)) = key
+disposerKey (STMSimpleDisposer (TSimpleDisposerElement key _ _)) = key
 disposerKey (ResourceManagerDisposer resourceManager) = resourceManagerKey resourceManager
 
 
 disposerFinalizers :: DisposerElement -> Finalizers
 disposerFinalizers (IODisposer _ _ _ _ finalizers) = finalizers
 disposerFinalizers (STMDisposer (TDisposerElement _ _ _ _ finalizers)) = finalizers
+disposerFinalizers (STMSimpleDisposer (TSimpleDisposerElement _ _ finalizers)) = finalizers
 disposerFinalizers (ResourceManagerDisposer rm) = resourceManagerFinalizers rm
 
 
@@ -259,11 +310,15 @@ newUnmanagedResourceManagerSTM worker exChan = do
 
 
 attachResource :: (MonadSTM' r CanThrow m, Resource a) => ResourceManager -> a -> m ()
-attachResource resourceManager (toDisposer -> Disposer ds) = liftSTM' do
-  mapM_ (attachDisposer resourceManager) ds
+attachResource resourceManager disposer = liftSTM' do
+  either throwM pure =<< tryAttachResource resourceManager disposer
+
+tryAttachResource :: (MonadSTM' r t m, Resource a) => ResourceManager -> a -> m (Either FailedToAttachResource ())
+tryAttachResource resourceManager (toDisposer -> Disposer ds) = liftSTM' do
+  sequence_ <$> mapM (tryAttachDisposer resourceManager) ds
 
-attachDisposer :: ResourceManager -> DisposerElement -> STM' r CanThrow ()
-attachDisposer resourceManager disposer = do
+tryAttachDisposer :: ResourceManager -> DisposerElement -> STM' r t (Either FailedToAttachResource ())
+tryAttachDisposer resourceManager disposer = do
   readTVar (resourceManagerState resourceManager) >>= \case
     ResourceManagerNormal attachedResources _ _ -> do
       alreadyAttached <- isJust . HM.lookup key <$> readTVar attachedResources
@@ -271,7 +326,8 @@ attachDisposer resourceManager disposer = do
         -- Returns false if the disposer is already finalized
         attachedFinalizer <- registerFinalizer (disposerFinalizers disposer) finalizer
         when attachedFinalizer $ modifyTVar attachedResources (HM.insert key disposer)
-    _ -> throwM $ userError "failed to attach resource" -- TODO throw proper exception
+      pure $ Right ()
+    _ -> pure $ Left FailedToAttachResource
   where
     key :: Unique
     key = disposerKey disposer
@@ -311,7 +367,7 @@ beginDisposeResourceManagerInternal rm = do
       -- Await indirect dependencies
       awaitDisposeDependencies $ DisposeDependencies rmKey (pure dependencies)
       -- Set state to disposed and run finalizers
-      atomically do
+      atomically' do
         writeTVar (resourceManagerState rm) ResourceManagerDisposed
         runFinalizers (resourceManagerFinalizers rm)
 
@@ -323,6 +379,8 @@ beginDisposeResourceManagerInternal rm = do
       DisposeResultAwait <$> beginDisposeFnDisposer worker exChan state finalizers
     resourceManagerBeginDispose (STMDisposer disposer) =
       DisposeResultAwait <$> beginDisposeSTMDisposer disposer
+    resourceManagerBeginDispose (STMSimpleDisposer disposer) =
+      DisposeResultAwait (pure ()) <$ disposeTSimpleDisposerElement disposer
     resourceManagerBeginDispose (ResourceManagerDisposer resourceManager) =
       DisposeResultDependencies <$> beginDisposeResourceManagerInternal resourceManager
 
@@ -371,16 +429,16 @@ registerFinalizer (Finalizers finalizerVar) finalizer =
       pure True
     Nothing -> pure False
 
-runFinalizers :: Finalizers -> STM ()
+runFinalizers :: Finalizers -> STM' r t ()
 runFinalizers (Finalizers finalizerVar) = do
   readTVar finalizerVar >>= \case
     Just finalizers -> do
       noRetry $ noThrow $ sequence_ finalizers
       writeTVar finalizerVar Nothing
-    Nothing -> throwM $ userError "runFinalizers was called multiple times (it must only be run once)"
+    Nothing -> traceM "runFinalizers was called multiple times (it must only be run once)"
 
 runFinalizersShortIO :: Finalizers -> ShortIO ()
-runFinalizersShortIO finalizers = unsafeShortIO $ atomically $ runFinalizers finalizers
+runFinalizersShortIO finalizers = unsafeShortIO $ atomically' $ runFinalizers finalizers
 
 runFinalizersAfter :: Finalizers -> Future () -> ShortIO ()
 runFinalizersAfter finalizers awaitable = do
@@ -392,4 +450,4 @@ runFinalizersAfter finalizers awaitable = do
     else
       void $ forkIOShortIO do
         await awaitable
-        atomically $ runFinalizers finalizers
+        atomically' $ runFinalizers finalizers
-- 
GitLab