packages feed

resource-registry 0.1.0.0 → 0.1.1.0

raw patch · 7 files changed

+1916/−1852 lines, 7 filesdep −si-timersdep −strict-mvardep −strict-stmdep ~containersdep ~io-classesdep ~mtl

Dependencies removed: si-timers, strict-mvar, strict-stm

Dependency ranges changed: containers, io-classes, mtl

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history of strict-checked-vars +## 0.1.1.0 — 2025-05-15++* Use `io-classes-1.8`.+ ## 0.1.0.0 — 2024-10-22  - First release, extracted from [`ouroboros-consensus`](https://github.com/IntersectMBO/ouroboros-consensus).
resource-registry.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: resource-registry-version: 0.1.0.0+version: 0.1.1.0 synopsis: Track allocated resources description:   When the scope of a @bracket@ doesn't enclose all uses of the resource, a@@ -21,7 +21,7 @@ category: Control build-type: Simple bug-reports: https://github.com/IntersectMBO/io-classes-extra/issues-tested-with: ghc ==8.10 || ==9.2 || ==9.4 || ==9.6 || ==9.8 || ==9.10+tested-with: ghc ==9.6 || ==9.8 || ==9.10 || ==9.12 extra-doc-files:   CHANGELOG.md   README.md@@ -35,7 +35,7 @@   type: git   location: https://github.com/IntersectMBO/io-classes-extra   subdir: resource-registry-  tag: resource-registry-0.1.0.0+  tag: resource-registry-0.1.1.0  common warnings   ghc-options:@@ -54,13 +54,12 @@   import: warnings   exposed-modules: Control.ResourceRegistry   build-depends:-    base >=4.14 && <4.21,+    base >=4.14 && <4.22,     bimap ^>=0.5,-    containers >=0.6 && <0.8,-    io-classes ^>=1.5,-    mtl >=2.2 && <2.4,+    containers >=0.6.7 && <0.8,+    io-classes:{io-classes, strict-stm} ^>=1.8,+    mtl ^>=2.3,     nothunks ^>=0.2,-    strict-stm ^>=1.5,    hs-source-dirs: src   default-language: Haskell2010@@ -83,13 +82,10 @@     base,     containers,     generics-sop,-    io-classes,+    io-classes:{io-classes, si-timers, strict-mvar, strict-stm},     mtl,     quickcheck-state-machine:no-vendored-treediff,     resource-registry,-    si-timers,-    strict-mvar,-    strict-stm,     tasty,     tasty-quickcheck,     tree-diff,
src/Control/ResourceRegistry.hs view
@@ -12,1486 +12,1519 @@ {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE UndecidableInstances #-}--{-# OPTIONS_GHC -Wno-orphans            #-}---- | Note on terminology: when thread A forks thread B, we will say that thread A--- is the \"parent\" and thread B is the \"child\". No further relationship--- between the two threads is implied by this terminology. In particular, note--- that the child may outlive the parent. We will use \"fork\" and \"spawn\"--- interchangeably.------ = Motivation------ Whenever we allocate resources, we must keep track of them so that we can--- deallocate them when they are no longer required. The most important tool we--- have to achieve this is 'bracket':------ > bracket allocateResource releaseResource $ \r ->--- >   .. use r ..------ Often 'bracket' comes in the guise of a with-style combinator------ > withResource $ \r ->--- >   .. use r ..------ Where this pattern is applicable, it should be used and there is no need to--- use the 'ResourceRegistry'. However, 'bracket' introduces strict lexical--- scoping: the resource is available inside the scope of the bracket, and--- will be deallocated once we leave that scope. That pattern is sometimes--- hard to use.------ For example, suppose we have this interface to an SQL server------ > query :: Query -> IO QueryHandle--- > close :: QueryHandle -> IO ()--- > next  :: QueryHandle -> IO Row------ and suppose furthermore that we are writing a simple webserver that allows a--- client to send multiple SQL queries, get rows from any open query, and close--- queries when no longer required:------ > server :: IO ()--- > server = go Map.empty--- >   where--- >     go :: Map QueryId QueryHandle -> IO ()--- >     go handles = getRequest >>= \case--- >         New q -> do--- >           h   <- query q                        -- allocate--- >           qId <- generateQueryId--- >           sendResponse qId--- >           go $ Map.insert qId h handles--- >         Close qId -> do--- >           close (handles ! qId)                 -- release--- >           go $ Map.delete qId handles--- >         Next qId -> do--- >           sendResponse =<< next (handles ! qId)--- >           go handles------ The server opens and closes query handles in response to client requests.--- Restructuring this code to use 'bracket' would be awkward, but as it stands--- this code does not ensure that resources get deallocated; for example, if--- the server thread is killed ('killThread'), resources will be leaked.------ Another, perhaps simpler, example is spawning threads. Threads too should--- be considered to be resources that we should keep track of and deallocate--- when they are no longer required, primarily because when we deallocate--- (terminate) those threads they too will have a chance to deallocate /their/--- resources. As for other resources, we have a with-style combinator for this------ > withAsync $ \thread -> ..------ Lexical scoping of threads is often inconvenient, however, more so than for--- regular resources. The temptation is therefore to simply fork a thread and--- forget about it, but if we are serious about resource deallocation this is--- not an acceptable solution.------ = The resource registry------ The resource registry is essentially a piece of state tracking which--- resources have been allocated. The registry itself is allocated with a--- with-style combinator 'withRegistry', and when we leave that scope any--- resources not yet deallocated will be released at that point. Typically--- the registry is only used as a fall-back, ensuring that resources will--- deallocated even in the presence of exceptions. For example, here's how--- we might rewrite the above server example using a registry:------ > server' :: IO ()--- > server' =--- >     withRegistry $ \registry -> go registry Map.empty--- >   where--- >     go :: ResourceRegistry IO--- >        -> Map QueryId (ResourceKey, QueryHandle)--- >        -> IO ()--- >     go registry handles = getRequest >>= \case--- >         New q -> do--- >           (key, h) <- allocate registry (query q) close  -- allocate--- >           qId      <- generateQueryId--- >           sendResponse qId--- >           go registry $ Map.insert qId (key, h) handles--- >         Close qId -> do--- >           release registry (fst (handles ! qId))         -- release--- >           go registry $ Map.delete qId handles--- >         Next qId -> do--- >           sendResponse =<< next (snd (handles ! qId))--- >           go registry handles------ We allocate the query with the help of the registry, providing the registry--- with the means to deallocate the query should that be required. We can /and--- should/ still manually release resources also: in this particular example,--- the (lexical) scope of the registry is the entire server thread, so delaying--- releasing queries until we exit that scope will probably mean we hold on to--- resources for too long. The registry is only there as a fall-back.------ = Spawning threads------ We already observed in the introduction that insisting on lexical scoping--- for threads is often inconvenient, and that simply using--- 'Control.Monad.Class.MonadFork.forkIO' is no solution as it means we might--- leak resources. There is however another problem with--- 'Control.Monad.Class.MonadFork.forkIO'. Consider this snippet:------ > withRegistry $ \registry ->--- >   r <- allocate registry allocateResource releaseResource--- >   forkIO $ .. use r ..------ It is easy to see that this code is problematic: we allocate a resource @r@,--- then spawn a thread that uses @r@, and finally leave the scope of--- 'withRegistry', thereby deallocating @r@ -- leaving the thread to run with--- a now deallocated resource.------ It is /only/ safe for threads to use a given registry, and/or its registered--- resources, if the lifetime of those threads is tied to the lifetime of the--- registry. There would be no problem with the example above if the thread--- would be terminated when we exit the scope of 'withRegistry'.------ The 'forkThread' combinator provided by the registry therefore does two--- things: it allocates the thread as a resource in the registry, so that it can--- kill the thread when releasing all resources in the registry. It also records--- the thread ID in a set of known threads. Whenever the registry is accessed--- from a thread /not/ in this set, the registry throws a runtime exception,--- since such a thread might outlive the registry and hence its contents. The--- intention is that this guards against dangerous patterns like the one above.------ = Linking------ When thread A spawns thread B using 'withAsync', the lifetime of B is tied--- to the lifetime of A:------ > withAsync .. $ \threadB -> ..------ After all, when A exits the scope of the 'withAsync', thread B will be--- killed. The reverse is however not true: thread B can terminate before--- thread A. It is often useful for thread A to be able to declare a dependency--- on thread B: if B somehow fails, that is, terminates with an exception, we--- want that exception to be rethrown in thread A as well. A can achieve this--- by /linking/ to B:------ > withAsync .. $ \threadB -> do--- >   link threadB--- >   ..------ Linking a parent to a child is however of limited value if the lifetime of--- the child is not limited by the lifetime of the parent. For example, if A--- does------ > threadB <- async $ ..--- > link threadB------ and A terminates before B does, any exception thrown by B might be send to a--- thread that no longer exists. This is particularly problematic when we start--- chaining threads: if A spawns-and-links-to B which spawns-and-links-to C, and--- C throws an exception, perhaps the intention is that this gets rethrown to B,--- and then rethrown to A, terminating all three threads; however, if B has--- terminated before the exception is thrown, C will throw the exception to a--- non-existent thread and A is never notified.------ For this reason, the registry's 'linkToRegistry' combinator does not link the--- specified thread to the thread calling 'linkToRegistry', but rather to the--- thread that created the registry. After all, the lifetime of threads spawned--- with 'forkThread' can certainly exceed the lifetime of their parent threads,--- but the lifetime of /all/ threads spawned using the registry will be limited--- by the scope of that registry, and hence the lifetime of the thread that--- created it. So, when we call 'linkToRegistry', the exception will be thrown--- the thread that created the registry, which (if not caught) will cause that--- that to exit the scope of 'withRegistry', thereby terminating all threads in--- that registry.------ = Combining the registry and with-style allocation------ It is perfectly possible (indeed, advisable) to use 'bracket' and--- bracket-like allocation functions alongside the registry, but note that the--- usual caveats with 'bracket' and forking threads still applies. In--- particular, spawning threads inside the 'bracket' that make use of the--- bracketed resource is problematic; this is of course true whether or not a--- registry is used.------ In principle this also includes 'withAsync'; however, since 'withAsync'--- results in a thread that is not known to the registry, such a thread will not--- be able to use the registry (the registry would throw an unknown thread--- exception, as described above). For this purpose we provide 'withThread';--- 'withThread' (as opposed to 'forkThread') should be used when a parent thread--- wants to handle exceptions in the child thread; see 'withThread' for--- detailed discussion.------ It is /also/ fine to includes nested calls to 'withRegistry'. Since the--- lifetime of such a registry (and all resources within) is tied to the thread--- calling 'withRegistry', which itself is tied to the "parent registry" in--- which it was created, this creates a hierarchy of registries. It is of course--- essential for compositionality that we should be able to create local--- registries, but even if we do have easy access to a parent regisry, creating--- a local one where possibly is useful as it limits the scope of the resources--- created within, and hence their maximum lifetimes.--module Control.ResourceRegistry (-    -- * The resource registry proper-    Context-  , ResourceId-  , ResourceRegistry-    -- * Exceptions-  , RegistryClosedException (..)-  , ResourceRegistryThreadException-    -- * Creating and releasing the registry itself-  , bracketWithPrivateRegistry-  , registryThread-  , withRegistry-    -- * Allocating and releasing regular resources-  , ResourceKey-  , allocate-  , allocateEither-  , release-  , releaseAll-  , unsafeRelease-  , unsafeReleaseAll-    -- * Threads-  , Thread-  , cancelThread-  , forkLinkedThread-  , forkThread-  , linkToRegistry-  , threadId-  , waitAnyThread-  , waitThread-  , withThread-    -- * Temporary registry-  , TempRegistryException (..)-  , WithTempRegistry-  , allocateTemp-  , modifyWithTempRegistry-  , runInnerWithTempRegistry-  , runWithTempRegistry-    -- * Unsafe combinators primarily for testing-  , closeRegistry-  , countResources-  , unsafeNewRegistry-  ) where--import Control.Applicative ((<|>))-import Control.Concurrent.Class.MonadSTM.Strict-import Control.Exception (asyncExceptionFromException)-import Control.Monad-import Control.Monad.Class.MonadAsync-import Control.Monad.Class.MonadFork-import Control.Monad.Class.MonadThrow-import Control.Monad.Reader-import Control.Monad.State.Strict-import Data.Bifunctor-import Data.Bimap (Bimap)-import Data.Bimap qualified as Bimap-import Data.Either (partitionEithers)-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Data.Maybe (catMaybes, listToMaybe)-import Data.Set (Set)-import Data.Set qualified as Set-import Data.Void-import Data.Word (Word64)-import GHC.Generics (Generic)-import GHC.Stack (CallStack, HasCallStack)-import GHC.Stack qualified as GHC-import NoThunks.Class hiding (Context)---- | Tracks resources during their lifetime.-data ResourceRegistry m = ResourceRegistry {-      -- | Context in which the registry was created-      registryContext :: !(Context m)--      -- | Registry state-    , registryState   :: !(StrictTVar m (RegistryState m))-    }-  deriving (Generic)--deriving instance (forall a. NoThunks a => NoThunks (StrictTVar m a))-               => NoThunks (ResourceRegistry m)--{--------------------------------------------------------------------------------  Internal: registry state--------------------------------------------------------------------------------}---- | The age of a resource------ Age here is represented by an meaningless number. The one and only property--- that matters is that the age of resource A that was successfully allocated--- before resource B was (in the same registry) will be greater than the age of--- resource B.------ For the current implementation, that property will be true unless the--- registry lives long enough to have contained 2^64 separately allocated--- resources.------ This data is not exposed by the 'ResourceRegistry' interface.-newtype Age = Age Word64-  deriving stock   (Show)-  deriving newtype (Eq, Ord)-  deriving NoThunks via InspectHeapNamed "Age" Age---- | The age of the first resource successfully allocated in a fresh registry-ageOfFirstResource :: Age-ageOfFirstResource = Age maxBound---- | Map the age of the latest resource to be successfully allocated to the age--- of the next resource to be successfully allocated in the same registry-nextYoungerAge :: Age -> Age-nextYoungerAge (Age n) = Age (n - 1)---- | Internal registry state-data RegistryState m = RegistryState {-      -- | Forked threads-      registryThreads   :: !(KnownThreads m)--      -- | Currently allocated resources-      ---      -- INVARIANT: We record exactly the ages of currently allocated resources,-      -- @'Bimap.keys' . 'registryAges' = 'Map.keys' . 'registryResources'@.-    , registryResources :: !(Map ResourceId (Resource m))--      -- | Next available resource key-    , registryNextKey   :: !ResourceId--      -- | The age of each currently allocated resource-      ---      -- We use a 'Bimap' so we can maintain the keys in sorted order by age,-      -- which is necessary when closing the registry.-    , registryAges      :: !(Bimap ResourceId Age)--      -- | The age of the next resource-    , registryNextAge   :: !Age--      -- | Does the registry still accept new allocations?-      ---      -- See 'RegistryClosedException' for discussion.-    , registryStatus    :: !RegistryStatus-    }-  deriving (Generic, NoThunks)---- | The currently allocated keys in youngest-to-oldest order-getYoungestToOldest :: RegistryState m -> [ResourceId]-getYoungestToOldest = map snd . Bimap.toAscListR . registryAges---- | Threads known to the registry------ This is the set of threads spawned using 'forkThread'. The lifetimes of all--- of these threads are limited by the lifetime of the registry.------ Does not include the thread ID of the thread that created the registry. After--- all, this thread may well outlive the registry (though the registry cannot--- outlive it).------ Invariant (informal): the set of registered threads is a subset of the--- registered resources ('registryResources'). (This invariant is temporarily--- broken when we start a new thread in 'forkThread' but will be re-established--- before that thread starts execution proper.)-newtype KnownThreads m = KnownThreads (Set (ThreadId m))-  deriving NoThunks via InspectHeapNamed "KnownThreads" (KnownThreads m)---- | Status of the registry (open or closed)-data RegistryStatus =-    RegistryOpen--    -- | We record the 'CallStack' to the call to 'close-  | RegistryClosed !PrettyCallStack-  deriving (Generic, NoThunks)---- | Resource key------ Resource keys are tied to a particular registry.-data ResourceKey m = ResourceKey !(ResourceRegistry m) !ResourceId-  deriving Generic--deriving instance NoThunks (ResourceRegistry m)-               => NoThunks (ResourceKey m)---- | Return the 'ResourceId' of a 'ResourceKey'.-resourceKeyId :: ResourceKey m -> ResourceId-resourceKeyId (ResourceKey _rr rid) = rid---- | Resource ID------ This uniquifying data is not exposed by the 'ResourceRegistry' interface.-newtype ResourceId = ResourceId Int-  deriving stock   (Show, Eq, Ord)-  deriving newtype (Enum, NoThunks)---- | Information about a resource-data Resource m = Resource {-      -- | Context in which the resource was created-      resourceContext :: !(Context m)--      -- | Deallocate the resource-    , resourceRelease :: !(Release m)-    }-  deriving (Generic, NoThunks)---- | Release the resource, return 'True' when the resource was actually--- released, return 'False' when the resource was already released.------ If unsure, returning 'True' is always fine.-newtype Release m = Release (m Bool)-  deriving NoThunks via OnlyCheckWhnfNamed "Release" (Release m)--releaseResource :: Resource m -> m Bool-releaseResource Resource{resourceRelease = Release f} = f--instance Show (Release m) where-  show _ = "<<release>>"--{--------------------------------------------------------------------------------  Internal: pure functions on the registry state--------------------------------------------------------------------------------}--modifyKnownThreads ::-     (Set (ThreadId m) -> Set (ThreadId m))-  -> KnownThreads m-  -> KnownThreads m-modifyKnownThreads f (KnownThreads ts) = KnownThreads (f ts)---- | Auxiliary for functions that should be disallowed when registry is closed-unlessClosed ::-     State (RegistryState m) a-  -> State (RegistryState m) (Either PrettyCallStack a)-unlessClosed f = do-    status <- gets registryStatus-    case status of-      RegistryClosed closed -> return $ Left closed-      RegistryOpen          -> Right <$> f---- | Allocate key for new resource-allocKey :: State (RegistryState m) (Either PrettyCallStack ResourceId)-allocKey = unlessClosed $ do-    nextKey <- gets registryNextKey-    modify $ \st -> st {registryNextKey = succ nextKey}-    return nextKey---- | Insert new resource-insertResource ::-     ResourceId-  -> Resource m-  -> State (RegistryState m) (Either PrettyCallStack ())-insertResource key r = unlessClosed $ do-    modify $ \st -> st {-        registryResources = Map.insert key r (registryResources st)-      , registryAges      = Bimap.insert-                              key-                              (registryNextAge st)-                              (registryAges st)-      , registryNextAge   = nextYoungerAge (registryNextAge st)-      }---- | Remove resource from the registry (if it exists)-removeResource :: ResourceId -> State (RegistryState m) (Maybe (Resource m))-removeResource key = state $ \st ->-    let (mbResource, resources') = Map.updateLookupWithKey-                                     (\_ _ -> Nothing)-                                     key-                                     (registryResources st)--        st' = st {-            registryResources = resources'-          , registryAges      = Bimap.delete key (registryAges st)-          }-    in  (mbResource, st')---- | Insert thread into the set of known threads-insertThread :: MonadThread m => ThreadId m -> State (RegistryState m) ()-insertThread tid =-    modify $ \st -> st {-        registryThreads = modifyKnownThreads (Set.insert tid) $-                            registryThreads st-      }---- | Remove thread from set of known threads-removeThread :: MonadThread m => ThreadId m -> State (RegistryState m) ()-removeThread tid =-    modify $ \st -> st {-        registryThreads = modifyKnownThreads (Set.delete tid) $-                            registryThreads st-      }---- | Close the registry------ Returns the keys currently allocated if the registry is not already closed.------ POSTCONDITION: They are returned in youngest-to-oldest order.-close ::-     PrettyCallStack-  -> State (RegistryState m) (Either PrettyCallStack [ResourceId])-close closeCallStack = unlessClosed $ do-    modify $ \st -> st {registryStatus = RegistryClosed closeCallStack}-    gets getYoungestToOldest---- | Convenience function for updating the registry state-updateState ::-     forall m a.-     MonadSTM m-  => ResourceRegistry m-  -> State (RegistryState m) a-  -> m a-updateState rr f =-    atomically $ stateTVar (registryState rr) (runState f)---- | Attempt to allocate a resource in a registry which is closed------ When calling 'closeRegistry' (typically, leaving the scope of--- 'withRegistry'), all resources in the registry must be released. If a--- concurrent thread is still allocating resources, we end up with a race--- between the thread trying to allocate new resources and the registry trying--- to free them all. To avoid this, before releasing anything, the registry will--- record itself as closed. Any attempt by a concurrent thread to allocate a new--- resource will then result in a 'RegistryClosedException'.------ It is probably not particularly useful for threads to try and catch this--- exception (apart from in a generic handler that does local resource cleanup).--- The thread will anyway soon receive a 'Control.Exception.ThreadKilled'--- exception.-data RegistryClosedException =-    forall m. MonadThread m => RegistryClosedException {-        -- | The context in which the registry was created-        registryClosedRegistryContext :: !(Context m)--        -- | Callstack to the call to 'closeRegistry'-        ---        -- Note that 'closeRegistry' can only be called from the same thread-        -- that created the registry.-      , registryClosedCloseCallStack  :: !PrettyCallStack--        -- | Context of the call resulting in the exception-      , registryClosedAllocContext    :: !(Context m)-      }--deriving instance Show RegistryClosedException-instance Exception RegistryClosedException--{--------------------------------------------------------------------------------  Creating and releasing the registry itself--------------------------------------------------------------------------------}---- | Create a new registry------ You are strongly encouraged to use 'withRegistry' instead.--- Exported primarily for the benefit of tests.-unsafeNewRegistry ::-     (MonadSTM m, MonadThread m, HasCallStack)-  => m (ResourceRegistry m)-unsafeNewRegistry = do-    context  <- captureContext-    stateVar <- newTVarIO initState-    return ResourceRegistry {-          registryContext = context-        , registryState   = stateVar-        }-  where-    initState :: RegistryState m-    initState = RegistryState {-          registryThreads   = KnownThreads Set.empty-        , registryResources = Map.empty-        , registryNextKey   = ResourceId 1-        , registryAges      = Bimap.empty-        , registryNextAge   = ageOfFirstResource-        , registryStatus    = RegistryOpen-        }---- | Close the registry------ This can only be called from the same thread that created the registry.--- This is a no-op if the registry is already closed.------ This entire function runs with exceptions masked, so that we are not--- interrupted while we release all resources.------ Resources will be allocated from young to old, so that resources allocated--- later can safely refer to resources created earlier.------ The release functions are run in the scope of an exception handler, so that--- if releasing one resource throws an exception, we still attempt to release--- the other resources. Should we catch an exception whilst we close the--- registry, we will rethrow it after having attempted to release all resources.--- If there is more than one, we will pick a random one to rethrow, though we--- will prioritize asynchronous exceptions over other exceptions. This may be--- important for exception handlers that catch all-except-asynchronous--- exceptions.-closeRegistry ::-     (MonadMask m, MonadThread m, MonadSTM m, HasCallStack)-  => ResourceRegistry m-  -> m ()-closeRegistry rr = mask_ $ do-    context <- captureContext-    unless (contextThreadId context == contextThreadId (registryContext rr)) $-      throwIO $ ResourceRegistryClosedFromWrongThread {-          resourceRegistryCreatedIn = registryContext rr-        , resourceRegistryUsedIn    = context-        }--    -- Close the registry so that we cannot allocate any further resources-    alreadyClosed <- updateState rr $ close (contextCallStack context)-    case alreadyClosed of-      Left _ ->-        return ()-      Right keys -> do-        -- At this point we have not /removed/ any elements from the map,-        -- allowing concurrent threads to do their own cleanup of resources-        -- (this may for instance be important if a thread deallocates its-        -- resources in a particular order -- note that cancelling a thread-        -- is a synchronous operation, so we will wait for it to finish-        -- releasing its resources.)-        -- /If/ a concurrent thread does some cleanup, then some of the calls-        -- to 'release' that we do here might be no-ops.-       void $ releaseResources rr keys release---- | Helper for 'closeRegistry', 'releaseAll', and 'unsafeReleaseAll': release--- the resources allocated with the given 'ResourceId's.------ Returns the contexts of the resources that were actually released.-releaseResources ::-     MonadCatch m-  => ResourceRegistry m-  -> [ResourceId]-     -- ^ PRECONDITION: The currently allocated keys,-     -- youngest-to-oldest-  -> (ResourceKey m -> m (Maybe (Context m)))-     -- ^ How to release the resource, e.g., 'release' or-     -- 'unsafeRelease'.-  ->  m [Context m]-releaseResources rr sortedKeys releaser = do-    (exs, mbContexts) <- fmap partitionEithers $-      forM sortedKeys $ try . releaser . ResourceKey rr--    case prioritize exs of-      Nothing -> return (catMaybes mbContexts)-      Just e  -> throwIO e-  where-    prioritize :: [SomeException] -> Maybe SomeException-    prioritize =-          (\(asyncEx, otherEx) -> listToMaybe asyncEx <|> listToMaybe otherEx)-        . first catMaybes-        . unzip-        . map (\e -> (asyncExceptionFromException e, e))---- | Create a new registry------ See documentation of 'ResourceRegistry' for a detailed discussion.-withRegistry ::-     (MonadSTM m, MonadMask m, MonadThread m, HasCallStack)-  => (ResourceRegistry m -> m a)-  -> m a-withRegistry = bracket unsafeNewRegistry closeRegistry---- | Create a new private registry for use by a bracketed resource------ Use this combinator as a more specific and easier-to-maintain alternative to--- the following.------ > 'withRegistry' $ \rr ->--- >   'bracket' (newFoo rr) closeFoo $ \foo ->--- >     (... rr does not occur in this scope ...)------ NB The scoped body can use `withRegistry` if it also needs its own, separate--- registry.------ Use this combinator to emphasize that the registry is private to (ie only--- used by and/or via) the bracketed resource and that it thus has nearly the--- same lifetime. This combinator ensures the following specific invariants--- regarding lifetimes and order of releases.------ o The registry itself is older than the bracketed resource.------ o The only registered resources older than the bracketed resource were---   allocated in the registry by the function that allocated the bracketed---   resource.------ o Because of the older resources, the bracketed resource is itself also---   registered in the registry; that's the only way we can be sure to release---   all resources in the right order.------ NB Because the registry is private to the resource, the @a@ type could save--- the handle to @registry@ and safely close the registry if the scoped body--- calls @closeA@ before the bracket ends. Though we have not used the type--- system to guarantee that the interface of the @a@ type cannot leak the--- registry to the body, this combinator does its part to keep the registry--- private to the bracketed resource.------ See documentation of 'ResourceRegistry' for a more general discussion.-bracketWithPrivateRegistry ::-     (MonadSTM m, MonadMask m, MonadThread m, HasCallStack)-  => (ResourceRegistry m -> m a)-  -> (a -> m ())  -- ^ Release the resource-  -> (a -> m r)-  -> m r-bracketWithPrivateRegistry newA closeA body =-    withRegistry $ \registry -> do-      (_key, a) <- allocate registry (\_key -> newA registry) closeA-      body a--{--------------------------------------------------------------------------------  Temporary registry--------------------------------------------------------------------------------}---- | Run an action with a temporary resource registry.------ When allocating resources that are meant to end up in some final state,--- e.g., stored in a 'Control.Monad.Class.MonadSTM.TVar', after which they are--- guaranteed to be released correctly, it is possible that an exception is--- thrown after allocating such a resource, but before it was stored in the--- final state. In that case, the resource would be leaked.--- 'runWithTempRegistry' solves that problem.------ When no exception is thrown before the end of 'runWithTempRegistry', the--- user must have transferred all the resources it allocated to their final--- state. This means that these resources don't have to be released by the--- temporary registry anymore, the final state is now in charge of releasing--- them.------ In case an exception is thrown before the end of 'runWithTempRegistry',--- /all/ resources allocated in the temporary registry will be released.------ Resources must be allocated using 'allocateTemp'.------ To make sure that the user doesn't forget to transfer a resource to the--- final state @st@, the user must pass a function to 'allocateTemp' that--- checks whether a given @st@ contains the resource, i.e., whether the--- resource was successfully transferred to its final destination.------ When no exception is thrown before the end of 'runWithTempRegistry', we--- check whether all allocated resources have been transferred to the final--- state @st@. If there's a resource that hasn't been transferred to the final--- state /and/ that hasn't be released or closed before (see the release--- function passed to 'allocateTemp'), a 'TempRegistryRemainingResource'--- exception will be thrown.------ For that reason, 'WithTempRegistry' is parameterised over the final state--- type @st@ and the given 'WithTempRegistry' action must return the final--- state.------ NOTE: we explicitly don't let 'runWithTempRegistry' return the final state,--- because the state /must/ have been stored somewhere safely, transferring--- the resources, before the temporary registry is closed.-runWithTempRegistry ::-     (MonadSTM m, MonadMask m, MonadThread m, HasCallStack)-  => WithTempRegistry st m (a, st)-  -> m a-runWithTempRegistry m = withRegistry $ \rr -> do-    varTransferredTo <- newTVarIO mempty-    let tempRegistry = TempRegistry {-            tempResourceRegistry = rr-          , tempTransferredTo    = varTransferredTo-          }-    (a, st) <- runReaderT (unWithTempRegistry m) tempRegistry-    -- We won't reach this point if an exception is thrown, so we won't check-    -- for remaining resources in that case.-    ---    -- No need to mask here, whether we throw the async exception or-    -- 'TempRegistryRemainingResource' doesn't matter.-    transferredTo <- readTVarIO varTransferredTo-    untrackTransferredTo rr transferredTo st--    context <- captureContext-    remainingResources <- releaseAllHelper rr context release--    whenJust (listToMaybe remainingResources) $ \remainingResource ->-      throwIO $ TempRegistryRemainingResource {-          tempRegistryContext  = registryContext rr-        , tempRegistryResource = remainingResource-        }-    return a--    where-        whenJust (Just x) f = f x-        whenJust Nothing _  = pure ()---- | Embed a self-contained 'WithTempRegistry' computation into a larger one.------ The internal 'WithTempRegistry' is effectively passed to--- 'runWithTempRegistry'. It therefore must have no dangling resources, for--- example. This is the meaning of /self-contained/ above.------ The key difference beyond 'runWithTempRegistry' is that the resulting--- composite resource is also guaranteed to be registered in the outer--- 'WithTempRegistry' computation's registry once the inner registry is closed.--- Combined with the following assumption, this establishes the invariant that--- all resources are (transitively) in a temporary registry.------ As the resource might require some implementation details to be closed, the--- function to close it will also be provided by the inner computation.------ ASSUMPTION: closing @res@ closes every resource contained in @innerSt@------ NOTE: In the current implementation, there will be a brief moment where the--- inner registry still contains the inner computation's resources and also the--- outer registry simultaneously contains the new composite resource. If an--- async exception is received at that time, then the inner resources will be--- closed and then the composite resource will be closed. This means there's a--- risk of /double freeing/, which can be harmless if anticipated.-runInnerWithTempRegistry ::-     forall innerSt st m res a.-     (MonadSTM m, MonadMask m, MonadThread m)-  => WithTempRegistry innerSt m (a, innerSt, res)-     -- ^ The embedded computation; see ASSUMPTION above-  -> (res -> m Bool)-     -- ^ How to free; same as for 'allocateTemp'-  -> (st -> res -> Bool)-     -- ^ How to check; same as for 'allocateTemp'-  -> WithTempRegistry st m a-runInnerWithTempRegistry inner free isTransferred = do-    outerTR <- WithTempRegistry ask--    lift $ runWithTempRegistry $ do-      (a, innerSt, res) <- inner--      -- Allocate in the outer layer.-      _ <-   withFixedTempRegistry outerTR-           $ allocateTemp (return res) free isTransferred--      -- TODO This point here is where an async exception could cause both the-      -- inner resources to be closed and the outer resource to be closed later.-      ---      -- If we want to do better than that, we'll need a variant of-      -- 'runWithTempRegistry' that lets us perform some action with async-      -- exceptions masked "at the same time" it closes its registry.--      -- Note that everything in `inner` allocated via `allocateTemp` must-      -- either be closed or else present in `innerSt` by this point ---      -- `runWithTempRegistry` would have thrown if not.-      pure (a, innerSt)-  where-    withFixedTempRegistry ::-           TempRegistry     st      m-        -> WithTempRegistry st      m res-        -> WithTempRegistry innerSt m res-    withFixedTempRegistry env (WithTempRegistry (ReaderT f)) =-      WithTempRegistry $ ReaderT $ \_ -> f env---- | When 'runWithTempRegistry' exits successfully while there are still--- resources remaining in the temporary registry that haven't been transferred--- to the final state.-data TempRegistryException =-    forall m. MonadThread m => TempRegistryRemainingResource {-        -- | The context in which the temporary registry was created.-        tempRegistryContext  :: !(Context m)--        -- | The context in which the resource was allocated that was not-        -- transferred to the final state.-      , tempRegistryResource :: !(Context m)-      }--deriving instance Show TempRegistryException-instance Exception TempRegistryException---- | Given a final state, return the 'ResourceId's of the resources that have--- been /transferred to/ that state.-newtype TransferredTo st = TransferredTo {-      runTransferredTo :: st -> Set ResourceId-    }-  deriving newtype (Semigroup, Monoid)-  deriving NoThunks via OnlyCheckWhnfNamed "TransferredTo" (TransferredTo st)---- | The environment used to run a 'WithTempRegistry' action.-data TempRegistry st m = TempRegistry {-      tempResourceRegistry :: !(ResourceRegistry m)-    , tempTransferredTo    :: !(StrictTVar m (TransferredTo st))-      -- ^ Used as a @Writer@.-    }---- | An action with a temporary registry in scope, see 'runWithTempRegistry'--- for more details.------ The most important function to run in this monad is 'allocateTemp'.-newtype WithTempRegistry st m a = WithTempRegistry {-      unWithTempRegistry :: ReaderT (TempRegistry st m) m a-    }-  deriving newtype ( Functor-                   , Applicative-                   , Monad-                   , MonadThrow-                   , MonadCatch-                   , MonadMask-                   )--instance MonadTrans (WithTempRegistry st) where-  lift = WithTempRegistry . lift--instance MonadState s m => MonadState s (WithTempRegistry st m) where-  state = WithTempRegistry . state---- | Untrack all the resources from the registry that have been transferred to--- the given state.------ Untracking a resource means removing it from the registry without releasing--- it.------ NOTE: does not check that it's called by the same thread that allocated the--- resources, as it's an internal function only used in 'runWithTempRegistry'.-untrackTransferredTo ::-     MonadSTM m-  => ResourceRegistry m-  -> TransferredTo st-  -> st-  -> m ()-untrackTransferredTo rr transferredTo st =-    updateState rr $ mapM_ removeResource rids-  where-    rids = runTransferredTo transferredTo st---- | Allocate a resource in a temporary registry until it has been transferred--- to the final state @st@. See 'runWithTempRegistry' for more details.-allocateTemp ::-     (MonadSTM m, MonadMask m, MonadThread m, HasCallStack)-  => m a-     -- ^ Allocate the resource-  -> (a -> m Bool)-     -- ^ Release the resource, return 'True' when the resource was actually-     -- released, return 'False' when the resource was already released.-     ---     -- Note that it is safe to always return 'True' when unsure.-  -> (st -> a -> Bool)-     -- ^ Check whether the resource is in the given state-  -> WithTempRegistry st m a-allocateTemp alloc free isTransferred = WithTempRegistry $ do-    TempRegistry rr varTransferredTo <- ask-    (key, a) <- lift (mustBeRight <$>-      allocateEither rr (fmap Right . const alloc) free)-    lift $ atomically $ modifyTVar varTransferredTo $ mappend $-      TransferredTo $ \st ->-        if isTransferred st a-        then Set.singleton (resourceKeyId key)-        else Set.empty-    return a---- | Higher level API on top of 'runWithTempRegistry': modify the given @st@,--- allocating resources in the process that will be transferred to the--- returned @st@.-modifyWithTempRegistry ::-     forall m st a.-     (MonadSTM m, MonadMask m, MonadThread m)-  => m st                                 -- ^ Get the state-  -> (st -> ExitCase st -> m ())          -- ^ Store the new state-  -> StateT st (WithTempRegistry st m) a  -- ^ Modify the state-  -> m a-modifyWithTempRegistry getSt putSt modSt = runWithTempRegistry $-    fst <$> generalBracket (lift getSt) transfer mutate-  where-    transfer :: st -> ExitCase (a, st) -> WithTempRegistry st m ()-    transfer initSt ec = lift $ putSt initSt (snd <$> ec)--    mutate :: st -> WithTempRegistry st m (a, st)-    mutate = runStateT modSt--{--------------------------------------------------------------------------------  Simple queries on the registry--------------------------------------------------------------------------------}---- | The thread that created the registry-registryThread :: ResourceRegistry m -> ThreadId m-registryThread = contextThreadId . registryContext---- | Number of currently allocated resources------ Primarily for the benefit of testing.-countResources :: MonadSTM m => ResourceRegistry m -> m Int-countResources rr = atomically $ aux <$> readTVar (registryState rr)-  where-    aux :: RegistryState m -> Int-    aux = Map.size . registryResources--{--------------------------------------------------------------------------------  Allocating resources--------------------------------------------------------------------------------}---- | Allocate new resource------ The allocation function will be run with asynchronous exceptions masked. This--- means that the resource allocation must either be fast or else interruptible;--- see "Dealing with Asynchronous Exceptions during Resource Acquisition"--- <http://www.well-typed.com/blog/97/> for details.-allocate ::-     forall m a.-     (MonadSTM m, MonadMask m, MonadThread m, HasCallStack)-  => ResourceRegistry m-  -> (ResourceId -> m a)-  -> (a -> m ())  -- ^ Release the resource-  -> m (ResourceKey m, a)-allocate rr alloc free = mustBeRight <$>-    allocateEither rr (fmap Right . alloc) (\a -> free a >> return True)---- | Generalization of 'allocate' for allocation functions that may fail-allocateEither ::-     forall m e a.-     (MonadSTM m, MonadMask m, MonadThread m, HasCallStack)-  => ResourceRegistry m-  -> (ResourceId -> m (Either e a))-  -> (a -> m Bool)-     -- ^ Release the resource, return 'True' when the resource-     -- hasn't been released or closed before.-  -> m (Either e (ResourceKey m, a))-allocateEither rr alloc free = do-    context <- captureContext-    ensureKnownThread rr context-    -- We check if the registry has been closed when we allocate the key, so-    -- that we can avoid needlessly allocating the resource.-    mKey <- updateState rr allocKey-    case mKey of-      Left closed ->-        throwRegistryClosed rr context closed-      Right key -> mask_ $ do-        ma <- alloc key-        case ma of-          Left  e -> return $ Left e-          Right a -> do-            -- TODO: Might want to have an exception handler around this call to-            -- 'updateState' just in case /that/ throws an exception.-            inserted <- updateState rr $-                 insertResource key (mkResource context a)-            case inserted of-              Left closed -> do-                -- Despite the earlier check, it's possible that the registry-                -- got closed after we allocated a new key but before we got a-                -- chance to register the resource. In this case, we must-                -- deallocate the resource again before throwing the exception.-                void $ free a-                throwRegistryClosed rr context closed-              Right () ->-                return $ Right (ResourceKey rr key, a)-  where-    mkResource :: Context m -> a -> Resource m-    mkResource context a = Resource {-          resourceContext = context-        , resourceRelease = Release $ free a-        }--throwRegistryClosed ::-     (MonadThrow m, MonadThread m)-  => ResourceRegistry m-  -> Context m-  -> PrettyCallStack-  -> m x-throwRegistryClosed rr context closed = throwIO RegistryClosedException {-      registryClosedRegistryContext = registryContext rr-    , registryClosedCloseCallStack  = closed-    , registryClosedAllocContext    = context-    }---- | Release resource------ This deallocates the resource and removes it from the registry. It will be--- the responsibility of the caller to make sure that the resource is no longer--- used in any thread.------ The deallocation function is run with exceptions masked, so that we are--- guaranteed not to remove the resource from the registry without releasing it.------ Releasing an already released resource is a no-op.------ When the resource has not been released before, its context is returned.-release ::-     (MonadMask m, MonadSTM m, MonadThread m, HasCallStack)-  => ResourceKey m-  -> m (Maybe (Context m))-release key@(ResourceKey rr _) = do-    context <- captureContext-    ensureKnownThread rr context-    unsafeRelease key---- | Unsafe version of 'release'------ The only difference between 'release' and 'unsafeRelease' is that the latter--- does not insist that it is called from a thread that is known to the--- registry. This is dangerous, because it implies that there is a thread with--- access to a resource which may be deallocated before that thread is--- terminated. Of course, we can't detect all such situations (when the thread--- merely uses a resource but does not allocate or release we can't tell), but--- normally when we /do/ detect this we throw an exception.------ This function should only be used if the above situation can be ruled out--- or handled by other means.-unsafeRelease ::-     (MonadMask m, MonadSTM m)-  => ResourceKey m-  -> m (Maybe (Context m))-unsafeRelease (ResourceKey rr rid) = do-    mask_ $ do-      mResource <- updateState rr $ removeResource rid-      case mResource of-        Nothing       -> return Nothing-        Just resource -> do-          actuallyReleased <- releaseResource resource-          return $-            if actuallyReleased-            then Just (resourceContext resource)-            else Nothing---- | Release all resources in the 'ResourceRegistry' without closing.------ See 'closeRegistry' for more details.-releaseAll ::-     (MonadMask m, MonadSTM m, MonadThread m, HasCallStack)-  => ResourceRegistry m-  -> m ()-releaseAll rr = do-    context <- captureContext-    unless (contextThreadId context == contextThreadId (registryContext rr)) $-      throwIO $ ResourceRegistryClosedFromWrongThread {-          resourceRegistryCreatedIn = registryContext rr-        , resourceRegistryUsedIn    = context-        }-    void $ releaseAllHelper rr context release---- | This is to 'releaseAll' what 'unsafeRelease' is to 'release': we do not--- insist that this funciton is called from a thread that is known to the--- registry. See 'unsafeRelease' for why this is dangerous.-unsafeReleaseAll ::-     (MonadMask m, MonadSTM m, MonadThread m, HasCallStack)-  => ResourceRegistry m-  -> m ()-unsafeReleaseAll rr = do-    context <- captureContext-    void $ releaseAllHelper rr context unsafeRelease---- | Internal helper used by 'releaseAll' and 'unsafeReleaseAll'.-releaseAllHelper ::-     (MonadMask m, MonadSTM m, MonadThread m)-  => ResourceRegistry m-  -> Context m-  -> (ResourceKey m -> m (Maybe (Context m)))-     -- ^ How to release a resource-  -> m [Context m]-releaseAllHelper rr context releaser = mask_ $ do-    mKeys <- updateState rr $ unlessClosed $ gets getYoungestToOldest-    case mKeys of-      Left closed -> throwRegistryClosed rr context closed-      Right keys  -> releaseResources rr keys releaser--{--------------------------------------------------------------------------------  Threads--------------------------------------------------------------------------------}---- | Thread------ The internals of this type are not exported.-data Thread m a = MonadThread m => Thread {-      -- | The underlying @async@ thread id-      threadId         :: !(ThreadId m)-    , threadResourceId :: !ResourceId-    , threadAsync      :: !(Async m a)-    , threadRegistry   :: !(ResourceRegistry m)-    }-  deriving NoThunks via OnlyCheckWhnfNamed "Thread" (Thread m a)---- | 'Eq' instance for 'Thread' compares 'threadId' only.-instance MonadThread m => Eq (Thread m a) where-  Thread{threadId = a} == Thread{threadId = b} = a == b---- | Cancel a thread------ This is a synchronous operation: the thread will have terminated when this--- function returns.------ Uses 'uninterruptibleCancel' because that's what 'withAsync' does.-cancelThread :: MonadAsync m => Thread m a -> m ()-cancelThread = uninterruptibleCancel . threadAsync---- | Wait for thread to terminate and return its result.------ If the thread throws an exception, this will rethrow that exception.------ NOTE: If A waits on B, and B is linked to the registry, and B throws an--- exception, then A might /either/ receive the exception thrown by B /or/--- the 'Control.Exception.ThreadKilled' exception thrown by the registry.-waitThread :: MonadAsync m => Thread m a -> m a-waitThread = wait . threadAsync---- | Lift 'waitAny' to 'Thread'-waitAnyThread :: forall m a. MonadAsync m => [Thread m a] -> m a-waitAnyThread ts = snd <$> waitAny (map threadAsync ts)---- | Fork a new thread-forkThread ::-     forall m a.-     (MonadMask m, MonadAsync m, HasCallStack)-  => ResourceRegistry m-  -> String  -- ^ Label for the thread-  -> m a-  -> m (Thread m a)-forkThread rr label body = snd <$>-    allocate rr (\key -> mkThread key <$> async (body' key)) cancelThread-  where-    mkThread :: ResourceId -> Async m a -> Thread m a-    mkThread rid child = Thread {-          threadId         = asyncThreadId child-        , threadResourceId = rid-        , threadAsync      = child-        , threadRegistry   = rr-        }--    body' :: ResourceId -> m a-    body' rid = do-        me <- myThreadId-        labelThread me label-        (registerThread me >> body) `finally` unregisterThread me rid--    -- Register the thread-    ---    -- We must add the thread to the list of known threads before the thread-    -- will start to use the registry.-    registerThread :: ThreadId m -> m ()-    registerThread tid = updateState rr $ insertThread tid--    -- Unregister the thread-    ---    -- Threads are the only kinds of resources that "deallocate themselves".-    -- We remove the thread from the resources as well as the set of known-    -- threads, so that these datastructures do not grow without bound.-    ---    -- This runs with asynchronous exceptions masked (due to 'finally'),-    -- though for the current implementation of 'unregisterThread' this-    -- makes no difference.-    unregisterThread :: ThreadId m -> ResourceId -> m ()-    unregisterThread tid rid =-        updateState rr $ do-          removeThread tid-          void $ removeResource rid---- | Bracketed version of 'forkThread'------ The analogue of 'withAsync' for the registry.------ Scoping thread lifetime using 'withThread' is important when a parent--- thread wants to link to a child thread /and handle any exceptions arising--- from the link/:------ > let handleLinkException :: ExceptionInLinkedThread -> m ()--- >     handleLinkException = ..--- > in handle handleLinkException $--- >      withThread registry codeInChild $ \child ->--- >        ..------ instead of------ > handle handleLinkException $ do  -- PROBABLY NOT CORRECT!--- >   child <- forkThread registry codeInChild--- >   ..------ where the parent may exit the scope of the exception handler before the child--- terminates. If the lifetime of the child cannot be limited to the lifetime of--- the parent, the child should probably be linked to the registry instead and--- the thread that spawned the registry should handle any exceptions.------ Note that in /principle/ there is no problem in using 'withAsync' alongside a--- registry. After all, in a pattern like------ > withRegistry $ \registry ->--- >   ..--- >   withAsync (.. registry ..) $ \async ->--- >     ..------ the async will be cancelled when leaving the scope of 'withAsync' and so--- that reference to the registry, or indeed any of the resources inside the--- registry, is safe. However, the registry implements a sanity check that the--- registry is only used from known threads. This is useful: when a thread that--- is not known to the registry (in other words, whose lifetime is not tied to--- the lifetime of the registry) spawns a resource in that registry, that--- resource may well be deallocated before the thread terminates, leading to--- undefined and hard to debug behaviour (indeed, whether or not this results in--- problems may well depend on precise timing); an exception that is thrown when--- /allocating/ the resource is (more) deterministic and easier to debug.--- Unfortunately, it means that the above pattern is not applicable, as the--- thread spawned by 'withAsync' is not known to the registry, and so if it were--- to try to use the registry, the registry would throw an error (even though--- this pattern is actually safe). This situation is not ideal, but for now we--- merely provide an alternative to 'withAsync' that /does/ register the thread--- with the registry.------ NOTE: Threads that are spawned out of the user's control but that must still--- make use of the registry can use the unsafe API. This should be used with--- caution, however.-withThread ::-     (MonadMask m, MonadAsync m)-  => ResourceRegistry m-  -> String  -- ^ Label for the thread-  -> m a-  -> (Thread m a -> m b)-  -> m b-withThread rr label body = bracket (forkThread rr label body) cancelThread---- | Link specified 'Thread' to the (thread that created) the registry-linkToRegistry :: (MonadAsync m, MonadFork m, MonadMask m) => Thread m a -> m ()-linkToRegistry t = linkTo (registryThread $ threadRegistry t) (threadAsync t)---- | Fork a thread and link to it to the registry.------ This function is just a convenience.-forkLinkedThread ::-     (MonadAsync m, MonadFork m, MonadMask m, HasCallStack)-  => ResourceRegistry m-  -> String  -- ^ Label for the thread-  -> m a-  -> m (Thread m a)-forkLinkedThread rr label body = do-    t <- forkThread rr label body-    -- There is no race condition here between the new thread throwing an-    -- exception and the 'linkToRegistry': if the thread /already/ threw the-    -- exception when we link it, the exception will be raised immediately-    -- (see 'linkTo' for details).-    linkToRegistry t-    return t--{--------------------------------------------------------------------------------  Check that registry is used from known thread--------------------------------------------------------------------------------}--ensureKnownThread ::-     forall m.-     (MonadThrow m, MonadThread m, MonadSTM m)-  => ResourceRegistry m-  -> Context m-  -> m ()-ensureKnownThread rr context = do-    isKnown <- checkIsKnown-    unless isKnown $-      throwIO $ ResourceRegistryUsedFromUntrackedThread {-                   resourceRegistryCreatedIn = registryContext rr-                 , resourceRegistryUsedIn    = context-                 }-  where-    checkIsKnown :: m Bool-    checkIsKnown-      | contextThreadId context == contextThreadId (registryContext rr) =-          return True-      | otherwise = atomically $ do-          KnownThreads ts <- registryThreads <$> readTVar (registryState rr)-          return $ contextThreadId context `Set.member` ts---- | Registry used from untracked threads------ If this exception is raised, it indicates a bug in the caller.-data ResourceRegistryThreadException =-    -- | If the registry is used from an untracked thread, we cannot do proper-    -- reference counting. The following threads are /tracked/: the thread-    -- that spawned the registry and all threads spawned by the registry.-    forall m. MonadThread m => ResourceRegistryUsedFromUntrackedThread {-          -- | Information about the context in which the registry was created-          resourceRegistryCreatedIn :: !(Context m)--          -- | The context in which it was used-        , resourceRegistryUsedIn    :: !(Context m)-        }--    -- | Registry closed from different threat than that created it-  | forall m. MonadThread m => ResourceRegistryClosedFromWrongThread {-          -- | Information about the context in which the registry was created-          resourceRegistryCreatedIn :: !(Context m)--          -- | The context in which it was used-        , resourceRegistryUsedIn    :: !(Context m)-        }--deriving instance Show ResourceRegistryThreadException-instance Exception ResourceRegistryThreadException--{--------------------------------------------------------------------------------  Auxiliary: context--------------------------------------------------------------------------------}---- | The internal context of a resource registry, recording a 'PrettyCallStack'--- of its creation and the creator's 'ThreadId'-data Context m = MonadThread m => Context {-      -- | CallStack in which it was created-      contextCallStack :: !PrettyCallStack--      -- | Thread that created the registry or resource-    , contextThreadId  :: !(ThreadId m)-    }---- Existential type; we can't use generics-instance NoThunks (Context m) where-  showTypeOf _ = "Context"-  wNoThunks ctxt (Context cs tid) = allNoThunks-    [ noThunks ctxt cs-    , noThunks ctxt (InspectHeapNamed @"ThreadId" tid)-    ]--deriving instance Show (Context m)--captureContext :: MonadThread m => HasCallStack => m (Context m)-captureContext = Context prettyCallStack <$> myThreadId--{--------------------------------------------------------------------------------  Misc utilities--------------------------------------------------------------------------------}---- | Generalization of 'link' that links an async to an arbitrary thread.------ Non standard (not in 'async' library)----linkTo ::-     (MonadAsync m, MonadFork m, MonadMask m)-  => ThreadId m-  -> Async m a-  -> m ()-linkTo tid = linkToOnly tid (not . isCancel)---- | Generalization of 'linkOnly' that links an async to an arbitrary thread.------ Non standard (not in 'async' library).----linkToOnly ::-     forall m a.-     (MonadAsync m, MonadFork m, MonadMask m)-  => ThreadId m-  -> (SomeException -> Bool)-  -> Async m a-  -> m ()-linkToOnly tid shouldThrow a = do-    void $ forkRepeat ("linkToOnly " <> show linkedThreadId) $ do-      r <- waitCatch a-      case r of-        Left e | shouldThrow e -> throwTo tid (exceptionInLinkedThread e)-        _otherwise             -> return ()-  where-    linkedThreadId :: ThreadId m-    linkedThreadId = asyncThreadId a--    exceptionInLinkedThread :: SomeException -> ExceptionInLinkedThread-    exceptionInLinkedThread =-        ExceptionInLinkedThread (show linkedThreadId)--isCancel :: SomeException -> Bool-isCancel e-  | Just AsyncCancelled <- fromException e = True-  | otherwise = False--forkRepeat :: (MonadFork m, MonadMask m) => String -> m a -> m (ThreadId m)-forkRepeat label action =-  mask $ \restore ->-    let go = do r <- tryAll (restore action)-                case r of-                  Left _ -> go-                  _      -> return ()-    in forkIO (labelThisThread label >> go)--tryAll :: MonadCatch m => m a -> m (Either SomeException a)-tryAll = try--mustBeRight :: Either Void a -> a-mustBeRight (Left  v) = absurd v-mustBeRight (Right a) = a--{--------------------------------------------------------------------------------  Auxiliary: CallStack with different Show instance--------------------------------------------------------------------------------}---- | CallStack with 'Show' instance using 'prettyCallStack'-newtype PrettyCallStack = PrettyCallStack CallStack-  deriving newtype (NoThunks)--instance Show PrettyCallStack where-  show (PrettyCallStack cs) = GHC.prettyCallStack cs---- | Capture a 'PrettyCallStack'-prettyCallStack :: HasCallStack => PrettyCallStack-prettyCallStack = PrettyCallStack GHC.callStack--{--------------------------------------------------------------------------------  Orphan instance--------------------------------------------------------------------------------}--instance (NoThunks k, NoThunks v)-      => NoThunks (Bimap k v) where+{-# OPTIONS_GHC -Wno-orphans #-}++-- | Note on terminology: when thread A forks thread B, we will say that thread A+-- is the \"parent\" and thread B is the \"child\". No further relationship+-- between the two threads is implied by this terminology. In particular, note+-- that the child may outlive the parent. We will use \"fork\" and \"spawn\"+-- interchangeably.+--+-- = Motivation+--+-- Whenever we allocate resources, we must keep track of them so that we can+-- deallocate them when they are no longer required. The most important tool we+-- have to achieve this is 'bracket':+--+-- > bracket allocateResource releaseResource $ \r ->+-- >   .. use r ..+--+-- Often 'bracket' comes in the guise of a with-style combinator+--+-- > withResource $ \r ->+-- >   .. use r ..+--+-- Where this pattern is applicable, it should be used and there is no need to+-- use the 'ResourceRegistry'. However, 'bracket' introduces strict lexical+-- scoping: the resource is available inside the scope of the bracket, and+-- will be deallocated once we leave that scope. That pattern is sometimes+-- hard to use.+--+-- For example, suppose we have this interface to an SQL server+--+-- > query :: Query -> IO QueryHandle+-- > close :: QueryHandle -> IO ()+-- > next  :: QueryHandle -> IO Row+--+-- and suppose furthermore that we are writing a simple webserver that allows a+-- client to send multiple SQL queries, get rows from any open query, and close+-- queries when no longer required:+--+-- > server :: IO ()+-- > server = go Map.empty+-- >   where+-- >     go :: Map QueryId QueryHandle -> IO ()+-- >     go handles = getRequest >>= \case+-- >         New q -> do+-- >           h   <- query q                        -- allocate+-- >           qId <- generateQueryId+-- >           sendResponse qId+-- >           go $ Map.insert qId h handles+-- >         Close qId -> do+-- >           close (handles ! qId)                 -- release+-- >           go $ Map.delete qId handles+-- >         Next qId -> do+-- >           sendResponse =<< next (handles ! qId)+-- >           go handles+--+-- The server opens and closes query handles in response to client requests.+-- Restructuring this code to use 'bracket' would be awkward, but as it stands+-- this code does not ensure that resources get deallocated; for example, if+-- the server thread is killed ('killThread'), resources will be leaked.+--+-- Another, perhaps simpler, example is spawning threads. Threads too should+-- be considered to be resources that we should keep track of and deallocate+-- when they are no longer required, primarily because when we deallocate+-- (terminate) those threads they too will have a chance to deallocate /their/+-- resources. As for other resources, we have a with-style combinator for this+--+-- > withAsync $ \thread -> ..+--+-- Lexical scoping of threads is often inconvenient, however, more so than for+-- regular resources. The temptation is therefore to simply fork a thread and+-- forget about it, but if we are serious about resource deallocation this is+-- not an acceptable solution.+--+-- = The resource registry+--+-- The resource registry is essentially a piece of state tracking which+-- resources have been allocated. The registry itself is allocated with a+-- with-style combinator 'withRegistry', and when we leave that scope any+-- resources not yet deallocated will be released at that point. Typically+-- the registry is only used as a fall-back, ensuring that resources will+-- deallocated even in the presence of exceptions. For example, here's how+-- we might rewrite the above server example using a registry:+--+-- > server' :: IO ()+-- > server' =+-- >     withRegistry $ \registry -> go registry Map.empty+-- >   where+-- >     go :: ResourceRegistry IO+-- >        -> Map QueryId (ResourceKey, QueryHandle)+-- >        -> IO ()+-- >     go registry handles = getRequest >>= \case+-- >         New q -> do+-- >           (key, h) <- allocate registry (query q) close  -- allocate+-- >           qId      <- generateQueryId+-- >           sendResponse qId+-- >           go registry $ Map.insert qId (key, h) handles+-- >         Close qId -> do+-- >           release registry (fst (handles ! qId))         -- release+-- >           go registry $ Map.delete qId handles+-- >         Next qId -> do+-- >           sendResponse =<< next (snd (handles ! qId))+-- >           go registry handles+--+-- We allocate the query with the help of the registry, providing the registry+-- with the means to deallocate the query should that be required. We can /and+-- should/ still manually release resources also: in this particular example,+-- the (lexical) scope of the registry is the entire server thread, so delaying+-- releasing queries until we exit that scope will probably mean we hold on to+-- resources for too long. The registry is only there as a fall-back.+--+-- = Spawning threads+--+-- We already observed in the introduction that insisting on lexical scoping+-- for threads is often inconvenient, and that simply using+-- 'Control.Monad.Class.MonadFork.forkIO' is no solution as it means we might+-- leak resources. There is however another problem with+-- 'Control.Monad.Class.MonadFork.forkIO'. Consider this snippet:+--+-- > withRegistry $ \registry ->+-- >   r <- allocate registry allocateResource releaseResource+-- >   forkIO $ .. use r ..+--+-- It is easy to see that this code is problematic: we allocate a resource @r@,+-- then spawn a thread that uses @r@, and finally leave the scope of+-- 'withRegistry', thereby deallocating @r@ -- leaving the thread to run with+-- a now deallocated resource.+--+-- It is /only/ safe for threads to use a given registry, and/or its registered+-- resources, if the lifetime of those threads is tied to the lifetime of the+-- registry. There would be no problem with the example above if the thread+-- would be terminated when we exit the scope of 'withRegistry'.+--+-- The 'forkThread' combinator provided by the registry therefore does two+-- things: it allocates the thread as a resource in the registry, so that it can+-- kill the thread when releasing all resources in the registry. It also records+-- the thread ID in a set of known threads. Whenever the registry is accessed+-- from a thread /not/ in this set, the registry throws a runtime exception,+-- since such a thread might outlive the registry and hence its contents. The+-- intention is that this guards against dangerous patterns like the one above.+--+-- = Linking+--+-- When thread A spawns thread B using 'withAsync', the lifetime of B is tied+-- to the lifetime of A:+--+-- > withAsync .. $ \threadB -> ..+--+-- After all, when A exits the scope of the 'withAsync', thread B will be+-- killed. The reverse is however not true: thread B can terminate before+-- thread A. It is often useful for thread A to be able to declare a dependency+-- on thread B: if B somehow fails, that is, terminates with an exception, we+-- want that exception to be rethrown in thread A as well. A can achieve this+-- by /linking/ to B:+--+-- > withAsync .. $ \threadB -> do+-- >   link threadB+-- >   ..+--+-- Linking a parent to a child is however of limited value if the lifetime of+-- the child is not limited by the lifetime of the parent. For example, if A+-- does+--+-- > threadB <- async $ ..+-- > link threadB+--+-- and A terminates before B does, any exception thrown by B might be send to a+-- thread that no longer exists. This is particularly problematic when we start+-- chaining threads: if A spawns-and-links-to B which spawns-and-links-to C, and+-- C throws an exception, perhaps the intention is that this gets rethrown to B,+-- and then rethrown to A, terminating all three threads; however, if B has+-- terminated before the exception is thrown, C will throw the exception to a+-- non-existent thread and A is never notified.+--+-- For this reason, the registry's 'linkToRegistry' combinator does not link the+-- specified thread to the thread calling 'linkToRegistry', but rather to the+-- thread that created the registry. After all, the lifetime of threads spawned+-- with 'forkThread' can certainly exceed the lifetime of their parent threads,+-- but the lifetime of /all/ threads spawned using the registry will be limited+-- by the scope of that registry, and hence the lifetime of the thread that+-- created it. So, when we call 'linkToRegistry', the exception will be thrown+-- the thread that created the registry, which (if not caught) will cause that+-- that to exit the scope of 'withRegistry', thereby terminating all threads in+-- that registry.+--+-- = Combining the registry and with-style allocation+--+-- It is perfectly possible (indeed, advisable) to use 'bracket' and+-- bracket-like allocation functions alongside the registry, but note that the+-- usual caveats with 'bracket' and forking threads still applies. In+-- particular, spawning threads inside the 'bracket' that make use of the+-- bracketed resource is problematic; this is of course true whether or not a+-- registry is used.+--+-- In principle this also includes 'withAsync'; however, since 'withAsync'+-- results in a thread that is not known to the registry, such a thread will not+-- be able to use the registry (the registry would throw an unknown thread+-- exception, as described above). For this purpose we provide 'withThread';+-- 'withThread' (as opposed to 'forkThread') should be used when a parent thread+-- wants to handle exceptions in the child thread; see 'withThread' for+-- detailed discussion.+--+-- It is /also/ fine to includes nested calls to 'withRegistry'. Since the+-- lifetime of such a registry (and all resources within) is tied to the thread+-- calling 'withRegistry', which itself is tied to the "parent registry" in+-- which it was created, this creates a hierarchy of registries. It is of course+-- essential for compositionality that we should be able to create local+-- registries, but even if we do have easy access to a parent regisry, creating+-- a local one where possibly is useful as it limits the scope of the resources+-- created within, and hence their maximum lifetimes.+module Control.ResourceRegistry+  ( -- * The resource registry proper+    Context+  , ResourceId+  , ResourceRegistry++    -- * Exceptions+  , RegistryClosedException (..)+  , ResourceRegistryThreadException++    -- * Creating and releasing the registry itself+  , bracketWithPrivateRegistry+  , registryThread+  , withRegistry++    -- * Allocating and releasing regular resources+  , ResourceKey+  , allocate+  , allocateEither+  , release+  , releaseAll+  , unsafeRelease+  , unsafeReleaseAll++    -- * Threads+  , Thread+  , cancelThread+  , forkLinkedThread+  , forkThread+  , linkToRegistry+  , threadId+  , waitAnyThread+  , waitThread+  , withThread++    -- * Temporary registry+  , TempRegistryException (..)+  , WithTempRegistry+  , allocateTemp+  , modifyWithTempRegistry+  , runInnerWithTempRegistry+  , runWithTempRegistry++    -- * Unsafe combinators primarily for testing+  , closeRegistry+  , countResources+  , unsafeNewRegistry+  ) where++import Control.Applicative ((<|>))+import Control.Concurrent.Class.MonadSTM.Strict+import Control.Exception (asyncExceptionFromException)+import Control.Monad+import Control.Monad.Class.MonadAsync+import Control.Monad.Class.MonadFork+import Control.Monad.Class.MonadThrow+import Control.Monad.Reader+import Control.Monad.State.Strict+import Data.Bifunctor+import Data.Bimap (Bimap)+import Data.Bimap qualified as Bimap+import Data.Either (partitionEithers)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Maybe (catMaybes, listToMaybe)+import Data.Set (Set)+import Data.Set qualified as Set+import Data.Void+import Data.Word (Word64)+import GHC.Generics (Generic)+import GHC.Stack (CallStack, HasCallStack)+import GHC.Stack qualified as GHC+import NoThunks.Class hiding (Context)++-- | Tracks resources during their lifetime.+data ResourceRegistry m = ResourceRegistry+  { registryContext :: !(Context m)+  -- ^ Context in which the registry was created+  , registryState :: !(StrictTVar m (RegistryState m))+  -- ^ Registry state+  }+  deriving Generic++deriving instance+  (forall a. NoThunks a => NoThunks (StrictTVar m a)) =>+  NoThunks (ResourceRegistry m)++{-------------------------------------------------------------------------------+  Internal: registry state+-------------------------------------------------------------------------------}++-- | The age of a resource+--+-- Age here is represented by an meaningless number. The one and only property+-- that matters is that the age of resource A that was successfully allocated+-- before resource B was (in the same registry) will be greater than the age of+-- resource B.+--+-- For the current implementation, that property will be true unless the+-- registry lives long enough to have contained 2^64 separately allocated+-- resources.+--+-- This data is not exposed by the 'ResourceRegistry' interface.+newtype Age = Age Word64+  deriving stock Show+  deriving newtype (Eq, Ord)+  deriving NoThunks via InspectHeapNamed "Age" Age++-- | The age of the first resource successfully allocated in a fresh registry+ageOfFirstResource :: Age+ageOfFirstResource = Age maxBound++-- | Map the age of the latest resource to be successfully allocated to the age+-- of the next resource to be successfully allocated in the same registry+nextYoungerAge :: Age -> Age+nextYoungerAge (Age n) = Age (n - 1)++-- | Internal registry state+data RegistryState m = RegistryState+  { registryThreads :: !(KnownThreads m)+  -- ^ Forked threads+  , registryResources :: !(Map ResourceId (Resource m))+  -- ^ Currently allocated resources+  --+  -- INVARIANT: We record exactly the ages of currently allocated resources,+  -- @'Bimap.keys' . 'registryAges' = 'Map.keys' . 'registryResources'@.+  , registryNextKey :: !ResourceId+  -- ^ Next available resource key+  , registryAges :: !(Bimap ResourceId Age)+  -- ^ The age of each currently allocated resource+  --+  -- We use a 'Bimap' so we can maintain the keys in sorted order by age,+  -- which is necessary when closing the registry.+  , registryNextAge :: !Age+  -- ^ The age of the next resource+  , registryStatus :: !RegistryStatus+  -- ^ Does the registry still accept new allocations?+  --+  -- See 'RegistryClosedException' for discussion.+  }+  deriving (Generic, NoThunks)++-- | The currently allocated keys in youngest-to-oldest order+getYoungestToOldest :: RegistryState m -> [ResourceId]+getYoungestToOldest = map snd . Bimap.toAscListR . registryAges++-- | Threads known to the registry+--+-- This is the set of threads spawned using 'forkThread'. The lifetimes of all+-- of these threads are limited by the lifetime of the registry.+--+-- Does not include the thread ID of the thread that created the registry. After+-- all, this thread may well outlive the registry (though the registry cannot+-- outlive it).+--+-- Invariant (informal): the set of registered threads is a subset of the+-- registered resources ('registryResources'). (This invariant is temporarily+-- broken when we start a new thread in 'forkThread' but will be re-established+-- before that thread starts execution proper.)+newtype KnownThreads m = KnownThreads (Set (ThreadId m))+  deriving NoThunks via InspectHeapNamed "KnownThreads" (KnownThreads m)++-- | Status of the registry (open or closed)+data RegistryStatus+  = RegistryOpen+  | -- | We record the 'CallStack' to the call to 'close+    RegistryClosed !PrettyCallStack+  deriving (Generic, NoThunks)++-- | Resource key+--+-- Resource keys are tied to a particular registry.+data ResourceKey m = ResourceKey !(ResourceRegistry m) !ResourceId+  deriving Generic++deriving instance+  NoThunks (ResourceRegistry m) =>+  NoThunks (ResourceKey m)++-- | Return the 'ResourceId' of a 'ResourceKey'.+resourceKeyId :: ResourceKey m -> ResourceId+resourceKeyId (ResourceKey _rr rid) = rid++-- | Resource ID+--+-- This uniquifying data is not exposed by the 'ResourceRegistry' interface.+newtype ResourceId = ResourceId Int+  deriving stock (Show, Eq, Ord)+  deriving newtype (Enum, NoThunks)++-- | Information about a resource+data Resource m = Resource+  { resourceContext :: !(Context m)+  -- ^ Context in which the resource was created+  , resourceRelease :: !(Release m)+  -- ^ Deallocate the resource+  }+  deriving (Generic, NoThunks)++-- | Release the resource, return 'True' when the resource was actually+-- released, return 'False' when the resource was already released.+--+-- If unsure, returning 'True' is always fine.+newtype Release m = Release (m Bool)+  deriving NoThunks via OnlyCheckWhnfNamed "Release" (Release m)++releaseResource :: Resource m -> m Bool+releaseResource Resource{resourceRelease = Release f} = f++instance Show (Release m) where+  show _ = "<<release>>"++{-------------------------------------------------------------------------------+  Internal: pure functions on the registry state+-------------------------------------------------------------------------------}++modifyKnownThreads ::+  (Set (ThreadId m) -> Set (ThreadId m)) ->+  KnownThreads m ->+  KnownThreads m+modifyKnownThreads f (KnownThreads ts) = KnownThreads (f ts)++-- | Auxiliary for functions that should be disallowed when registry is closed+unlessClosed ::+  State (RegistryState m) a ->+  State (RegistryState m) (Either PrettyCallStack a)+unlessClosed f = do+  status <- gets registryStatus+  case status of+    RegistryClosed closed -> return $ Left closed+    RegistryOpen -> Right <$> f++-- | Allocate key for new resource+allocKey :: State (RegistryState m) (Either PrettyCallStack ResourceId)+allocKey = unlessClosed $ do+  nextKey <- gets registryNextKey+  modify $ \st -> st{registryNextKey = succ nextKey}+  return nextKey++-- | Insert new resource+insertResource ::+  ResourceId ->+  Resource m ->+  State (RegistryState m) (Either PrettyCallStack ())+insertResource key r = unlessClosed $ do+  modify $ \st ->+    st+      { registryResources = Map.insert key r (registryResources st)+      , registryAges =+          Bimap.insert+            key+            (registryNextAge st)+            (registryAges st)+      , registryNextAge = nextYoungerAge (registryNextAge st)+      }++-- | Remove resource from the registry (if it exists)+removeResource :: ResourceId -> State (RegistryState m) (Maybe (Resource m))+removeResource key = state $ \st ->+  let (mbResource, resources') =+        Map.updateLookupWithKey+          (\_ _ -> Nothing)+          key+          (registryResources st)++      st' =+        st+          { registryResources = resources'+          , registryAges = Bimap.delete key (registryAges st)+          }+   in (mbResource, st')++-- | Insert thread into the set of known threads+insertThread :: MonadThread m => ThreadId m -> State (RegistryState m) ()+insertThread tid =+  modify $ \st ->+    st+      { registryThreads =+          modifyKnownThreads (Set.insert tid) $+            registryThreads st+      }++-- | Remove thread from set of known threads+removeThread :: MonadThread m => ThreadId m -> State (RegistryState m) ()+removeThread tid =+  modify $ \st ->+    st+      { registryThreads =+          modifyKnownThreads (Set.delete tid) $+            registryThreads st+      }++-- | Close the registry+--+-- Returns the keys currently allocated if the registry is not already closed.+--+-- POSTCONDITION: They are returned in youngest-to-oldest order.+close ::+  PrettyCallStack ->+  State (RegistryState m) (Either PrettyCallStack [ResourceId])+close closeCallStack = unlessClosed $ do+  modify $ \st -> st{registryStatus = RegistryClosed closeCallStack}+  gets getYoungestToOldest++-- | Convenience function for updating the registry state+updateState ::+  forall m a.+  MonadSTM m =>+  ResourceRegistry m ->+  State (RegistryState m) a ->+  m a+updateState rr f =+  atomically $ stateTVar (registryState rr) (runState f)++-- | Attempt to allocate a resource in a registry which is closed+--+-- When calling 'closeRegistry' (typically, leaving the scope of+-- 'withRegistry'), all resources in the registry must be released. If a+-- concurrent thread is still allocating resources, we end up with a race+-- between the thread trying to allocate new resources and the registry trying+-- to free them all. To avoid this, before releasing anything, the registry will+-- record itself as closed. Any attempt by a concurrent thread to allocate a new+-- resource will then result in a 'RegistryClosedException'.+--+-- It is probably not particularly useful for threads to try and catch this+-- exception (apart from in a generic handler that does local resource cleanup).+-- The thread will anyway soon receive a 'Control.Exception.ThreadKilled'+-- exception.+data RegistryClosedException+  = forall m. MonadThread m => RegistryClosedException+  { registryClosedRegistryContext :: !(Context m)+  -- ^ The context in which the registry was created+  , registryClosedCloseCallStack :: !PrettyCallStack+  -- ^ Callstack to the call to 'closeRegistry'+  --+  -- Note that 'closeRegistry' can only be called from the same thread+  -- that created the registry.+  , registryClosedAllocContext :: !(Context m)+  -- ^ Context of the call resulting in the exception+  }++deriving instance Show RegistryClosedException+instance Exception RegistryClosedException++{-------------------------------------------------------------------------------+  Creating and releasing the registry itself+-------------------------------------------------------------------------------}++-- | Create a new registry+--+-- You are strongly encouraged to use 'withRegistry' instead.+-- Exported primarily for the benefit of tests.+unsafeNewRegistry ::+  (MonadSTM m, MonadThread m, HasCallStack) =>+  m (ResourceRegistry m)+unsafeNewRegistry = do+  context <- captureContext+  stateVar <- newTVarIO initState+  return+    ResourceRegistry+      { registryContext = context+      , registryState = stateVar+      }+ where+  initState :: RegistryState m+  initState =+    RegistryState+      { registryThreads = KnownThreads Set.empty+      , registryResources = Map.empty+      , registryNextKey = ResourceId 1+      , registryAges = Bimap.empty+      , registryNextAge = ageOfFirstResource+      , registryStatus = RegistryOpen+      }++-- | Close the registry+--+-- This can only be called from the same thread that created the registry.+-- This is a no-op if the registry is already closed.+--+-- This entire function runs with exceptions masked, so that we are not+-- interrupted while we release all resources.+--+-- Resources will be allocated from young to old, so that resources allocated+-- later can safely refer to resources created earlier.+--+-- The release functions are run in the scope of an exception handler, so that+-- if releasing one resource throws an exception, we still attempt to release+-- the other resources. Should we catch an exception whilst we close the+-- registry, we will rethrow it after having attempted to release all resources.+-- If there is more than one, we will pick a random one to rethrow, though we+-- will prioritize asynchronous exceptions over other exceptions. This may be+-- important for exception handlers that catch all-except-asynchronous+-- exceptions.+closeRegistry ::+  (MonadMask m, MonadThread m, MonadSTM m, HasCallStack) =>+  ResourceRegistry m ->+  m ()+closeRegistry rr = mask_ $ do+  context <- captureContext+  unless (contextThreadId context == contextThreadId (registryContext rr)) $+    throwIO $+      ResourceRegistryClosedFromWrongThread+        { resourceRegistryCreatedIn = registryContext rr+        , resourceRegistryUsedIn = context+        }++  -- Close the registry so that we cannot allocate any further resources+  alreadyClosed <- updateState rr $ close (contextCallStack context)+  case alreadyClosed of+    Left _ ->+      return ()+    Right keys -> do+      -- At this point we have not /removed/ any elements from the map,+      -- allowing concurrent threads to do their own cleanup of resources+      -- (this may for instance be important if a thread deallocates its+      -- resources in a particular order -- note that cancelling a thread+      -- is a synchronous operation, so we will wait for it to finish+      -- releasing its resources.)+      -- /If/ a concurrent thread does some cleanup, then some of the calls+      -- to 'release' that we do here might be no-ops.+      void $ releaseResources rr keys release++-- | Helper for 'closeRegistry', 'releaseAll', and 'unsafeReleaseAll': release+-- the resources allocated with the given 'ResourceId's.+--+-- Returns the contexts of the resources that were actually released.+releaseResources ::+  MonadCatch m =>+  ResourceRegistry m ->+  -- | PRECONDITION: The currently allocated keys,+  -- youngest-to-oldest+  [ResourceId] ->+  -- | How to release the resource, e.g., 'release' or+  -- 'unsafeRelease'.+  (ResourceKey m -> m (Maybe (Context m))) ->+  m [Context m]+releaseResources rr sortedKeys releaser = do+  (exs, mbContexts) <-+    fmap partitionEithers $+      forM sortedKeys $+        try . releaser . ResourceKey rr++  case prioritize exs of+    Nothing -> return (catMaybes mbContexts)+    Just e -> throwIO e+ where+  prioritize :: [SomeException] -> Maybe SomeException+  prioritize =+    (\(asyncEx, otherEx) -> listToMaybe asyncEx <|> listToMaybe otherEx)+      . first catMaybes+      . unzip+      . map (\e -> (asyncExceptionFromException e, e))++-- | Create a new registry+--+-- See documentation of 'ResourceRegistry' for a detailed discussion.+withRegistry ::+  (MonadSTM m, MonadMask m, MonadThread m, HasCallStack) =>+  (ResourceRegistry m -> m a) ->+  m a+withRegistry = bracket unsafeNewRegistry closeRegistry++-- | Create a new private registry for use by a bracketed resource+--+-- Use this combinator as a more specific and easier-to-maintain alternative to+-- the following.+--+-- > 'withRegistry' $ \rr ->+-- >   'bracket' (newFoo rr) closeFoo $ \foo ->+-- >     (... rr does not occur in this scope ...)+--+-- NB The scoped body can use `withRegistry` if it also needs its own, separate+-- registry.+--+-- Use this combinator to emphasize that the registry is private to (ie only+-- used by and/or via) the bracketed resource and that it thus has nearly the+-- same lifetime. This combinator ensures the following specific invariants+-- regarding lifetimes and order of releases.+--+-- o The registry itself is older than the bracketed resource.+--+-- o The only registered resources older than the bracketed resource were+--   allocated in the registry by the function that allocated the bracketed+--   resource.+--+-- o Because of the older resources, the bracketed resource is itself also+--   registered in the registry; that's the only way we can be sure to release+--   all resources in the right order.+--+-- NB Because the registry is private to the resource, the @a@ type could save+-- the handle to @registry@ and safely close the registry if the scoped body+-- calls @closeA@ before the bracket ends. Though we have not used the type+-- system to guarantee that the interface of the @a@ type cannot leak the+-- registry to the body, this combinator does its part to keep the registry+-- private to the bracketed resource.+--+-- See documentation of 'ResourceRegistry' for a more general discussion.+bracketWithPrivateRegistry ::+  (MonadSTM m, MonadMask m, MonadThread m, HasCallStack) =>+  (ResourceRegistry m -> m a) ->+  -- | Release the resource+  (a -> m ()) ->+  (a -> m r) ->+  m r+bracketWithPrivateRegistry newA closeA body =+  withRegistry $ \registry -> do+    (_key, a) <- allocate registry (\_key -> newA registry) closeA+    body a++{-------------------------------------------------------------------------------+  Temporary registry+-------------------------------------------------------------------------------}++-- | Run an action with a temporary resource registry.+--+-- When allocating resources that are meant to end up in some final state,+-- e.g., stored in a 'Control.Monad.Class.MonadSTM.TVar', after which they are+-- guaranteed to be released correctly, it is possible that an exception is+-- thrown after allocating such a resource, but before it was stored in the+-- final state. In that case, the resource would be leaked.+-- 'runWithTempRegistry' solves that problem.+--+-- When no exception is thrown before the end of 'runWithTempRegistry', the+-- user must have transferred all the resources it allocated to their final+-- state. This means that these resources don't have to be released by the+-- temporary registry anymore, the final state is now in charge of releasing+-- them.+--+-- In case an exception is thrown before the end of 'runWithTempRegistry',+-- /all/ resources allocated in the temporary registry will be released.+--+-- Resources must be allocated using 'allocateTemp'.+--+-- To make sure that the user doesn't forget to transfer a resource to the+-- final state @st@, the user must pass a function to 'allocateTemp' that+-- checks whether a given @st@ contains the resource, i.e., whether the+-- resource was successfully transferred to its final destination.+--+-- When no exception is thrown before the end of 'runWithTempRegistry', we+-- check whether all allocated resources have been transferred to the final+-- state @st@. If there's a resource that hasn't been transferred to the final+-- state /and/ that hasn't be released or closed before (see the release+-- function passed to 'allocateTemp'), a 'TempRegistryRemainingResource'+-- exception will be thrown.+--+-- For that reason, 'WithTempRegistry' is parameterised over the final state+-- type @st@ and the given 'WithTempRegistry' action must return the final+-- state.+--+-- NOTE: we explicitly don't let 'runWithTempRegistry' return the final state,+-- because the state /must/ have been stored somewhere safely, transferring+-- the resources, before the temporary registry is closed.+runWithTempRegistry ::+  (MonadSTM m, MonadMask m, MonadThread m, HasCallStack) =>+  WithTempRegistry st m (a, st) ->+  m a+runWithTempRegistry m = withRegistry $ \rr -> do+  varTransferredTo <- newTVarIO mempty+  let tempRegistry =+        TempRegistry+          { tempResourceRegistry = rr+          , tempTransferredTo = varTransferredTo+          }+  (a, st) <- runReaderT (unWithTempRegistry m) tempRegistry+  -- We won't reach this point if an exception is thrown, so we won't check+  -- for remaining resources in that case.+  --+  -- No need to mask here, whether we throw the async exception or+  -- 'TempRegistryRemainingResource' doesn't matter.+  transferredTo <- readTVarIO varTransferredTo+  untrackTransferredTo rr transferredTo st++  context <- captureContext+  remainingResources <- releaseAllHelper rr context release++  whenJust (listToMaybe remainingResources) $ \remainingResource ->+    throwIO $+      TempRegistryRemainingResource+        { tempRegistryContext = registryContext rr+        , tempRegistryResource = remainingResource+        }+  return a+ where+  whenJust (Just x) f = f x+  whenJust Nothing _ = pure ()++-- | Embed a self-contained 'WithTempRegistry' computation into a larger one.+--+-- The internal 'WithTempRegistry' is effectively passed to+-- 'runWithTempRegistry'. It therefore must have no dangling resources, for+-- example. This is the meaning of /self-contained/ above.+--+-- The key difference beyond 'runWithTempRegistry' is that the resulting+-- composite resource is also guaranteed to be registered in the outer+-- 'WithTempRegistry' computation's registry once the inner registry is closed.+-- Combined with the following assumption, this establishes the invariant that+-- all resources are (transitively) in a temporary registry.+--+-- As the resource might require some implementation details to be closed, the+-- function to close it will also be provided by the inner computation.+--+-- ASSUMPTION: closing @res@ closes every resource contained in @innerSt@+--+-- NOTE: In the current implementation, there will be a brief moment where the+-- inner registry still contains the inner computation's resources and also the+-- outer registry simultaneously contains the new composite resource. If an+-- async exception is received at that time, then the inner resources will be+-- closed and then the composite resource will be closed. This means there's a+-- risk of /double freeing/, which can be harmless if anticipated.+runInnerWithTempRegistry ::+  forall innerSt st m res a.+  (MonadSTM m, MonadMask m, MonadThread m) =>+  -- | The embedded computation; see ASSUMPTION above+  WithTempRegistry innerSt m (a, innerSt, res) ->+  -- | How to free; same as for 'allocateTemp'+  (res -> m Bool) ->+  -- | How to check; same as for 'allocateTemp'+  (st -> res -> Bool) ->+  WithTempRegistry st m a+runInnerWithTempRegistry inner free isTransferred = do+  outerTR <- WithTempRegistry ask++  lift $ runWithTempRegistry $ do+    (a, innerSt, res) <- inner++    -- Allocate in the outer layer.+    _ <-+      withFixedTempRegistry outerTR $+        allocateTemp (return res) free isTransferred++    -- TODO This point here is where an async exception could cause both the+    -- inner resources to be closed and the outer resource to be closed later.+    --+    -- If we want to do better than that, we'll need a variant of+    -- 'runWithTempRegistry' that lets us perform some action with async+    -- exceptions masked "at the same time" it closes its registry.++    -- Note that everything in `inner` allocated via `allocateTemp` must+    -- either be closed or else present in `innerSt` by this point --+    -- `runWithTempRegistry` would have thrown if not.+    pure (a, innerSt)+ where+  withFixedTempRegistry ::+    TempRegistry st m ->+    WithTempRegistry st m res ->+    WithTempRegistry innerSt m res+  withFixedTempRegistry env (WithTempRegistry (ReaderT f)) =+    WithTempRegistry $ ReaderT $ \_ -> f env++-- | When 'runWithTempRegistry' exits successfully while there are still+-- resources remaining in the temporary registry that haven't been transferred+-- to the final state.+data TempRegistryException+  = forall m. MonadThread m => TempRegistryRemainingResource+  { tempRegistryContext :: !(Context m)+  -- ^ The context in which the temporary registry was created.+  , tempRegistryResource :: !(Context m)+  -- ^ The context in which the resource was allocated that was not+  -- transferred to the final state.+  }++deriving instance Show TempRegistryException+instance Exception TempRegistryException++-- | Given a final state, return the 'ResourceId's of the resources that have+-- been /transferred to/ that state.+newtype TransferredTo st = TransferredTo+  { runTransferredTo :: st -> Set ResourceId+  }+  deriving newtype (Semigroup, Monoid)+  deriving NoThunks via OnlyCheckWhnfNamed "TransferredTo" (TransferredTo st)++-- | The environment used to run a 'WithTempRegistry' action.+data TempRegistry st m = TempRegistry+  { tempResourceRegistry :: !(ResourceRegistry m)+  , tempTransferredTo :: !(StrictTVar m (TransferredTo st))+  -- ^ Used as a @Writer@.+  }++-- | An action with a temporary registry in scope, see 'runWithTempRegistry'+-- for more details.+--+-- The most important function to run in this monad is 'allocateTemp'.+newtype WithTempRegistry st m a = WithTempRegistry+  { unWithTempRegistry :: ReaderT (TempRegistry st m) m a+  }+  deriving newtype+    ( Functor+    , Applicative+    , Monad+    , MonadThrow+    , MonadCatch+    , MonadMask+    )++instance MonadTrans (WithTempRegistry st) where+  lift = WithTempRegistry . lift++instance MonadState s m => MonadState s (WithTempRegistry st m) where+  state = WithTempRegistry . state++-- | Untrack all the resources from the registry that have been transferred to+-- the given state.+--+-- Untracking a resource means removing it from the registry without releasing+-- it.+--+-- NOTE: does not check that it's called by the same thread that allocated the+-- resources, as it's an internal function only used in 'runWithTempRegistry'.+untrackTransferredTo ::+  MonadSTM m =>+  ResourceRegistry m ->+  TransferredTo st ->+  st ->+  m ()+untrackTransferredTo rr transferredTo st =+  updateState rr $ mapM_ removeResource rids+ where+  rids = runTransferredTo transferredTo st++-- | Allocate a resource in a temporary registry until it has been transferred+-- to the final state @st@. See 'runWithTempRegistry' for more details.+allocateTemp ::+  (MonadSTM m, MonadMask m, MonadThread m, HasCallStack) =>+  -- | Allocate the resource+  m a ->+  -- | Release the resource, return 'True' when the resource was actually+  -- released, return 'False' when the resource was already released.+  --+  -- Note that it is safe to always return 'True' when unsure.+  (a -> m Bool) ->+  -- | Check whether the resource is in the given state+  (st -> a -> Bool) ->+  WithTempRegistry st m a+allocateTemp alloc free isTransferred = WithTempRegistry $ do+  TempRegistry rr varTransferredTo <- ask+  (key, a) <-+    lift+      ( mustBeRight+          <$> allocateEither rr (fmap Right . const alloc) free+      )+  lift $+    atomically $+      modifyTVar varTransferredTo $+        mappend $+          TransferredTo $ \st ->+            if isTransferred st a+              then Set.singleton (resourceKeyId key)+              else Set.empty+  return a++-- | Higher level API on top of 'runWithTempRegistry': modify the given @st@,+-- allocating resources in the process that will be transferred to the+-- returned @st@.+modifyWithTempRegistry ::+  forall m st a.+  (MonadSTM m, MonadMask m, MonadThread m) =>+  -- | Get the state+  m st ->+  -- | Store the new state+  (st -> ExitCase st -> m ()) ->+  -- | Modify the state+  StateT st (WithTempRegistry st m) a ->+  m a+modifyWithTempRegistry getSt putSt modSt =+  runWithTempRegistry $+    fst <$> generalBracket (lift getSt) transfer mutate+ where+  transfer :: st -> ExitCase (a, st) -> WithTempRegistry st m ()+  transfer initSt ec = lift $ putSt initSt (snd <$> ec)++  mutate :: st -> WithTempRegistry st m (a, st)+  mutate = runStateT modSt++{-------------------------------------------------------------------------------+  Simple queries on the registry+-------------------------------------------------------------------------------}++-- | The thread that created the registry+registryThread :: ResourceRegistry m -> ThreadId m+registryThread = contextThreadId . registryContext++-- | Number of currently allocated resources+--+-- Primarily for the benefit of testing.+countResources :: MonadSTM m => ResourceRegistry m -> m Int+countResources rr = atomically $ aux <$> readTVar (registryState rr)+ where+  aux :: RegistryState m -> Int+  aux = Map.size . registryResources++{-------------------------------------------------------------------------------+  Allocating resources+-------------------------------------------------------------------------------}++-- | Allocate new resource+--+-- The allocation function will be run with asynchronous exceptions masked. This+-- means that the resource allocation must either be fast or else interruptible;+-- see "Dealing with Asynchronous Exceptions during Resource Acquisition"+-- <http://www.well-typed.com/blog/97/> for details.+allocate ::+  forall m a.+  (MonadSTM m, MonadMask m, MonadThread m, HasCallStack) =>+  ResourceRegistry m ->+  (ResourceId -> m a) ->+  -- | Release the resource+  (a -> m ()) ->+  m (ResourceKey m, a)+allocate rr alloc free =+  mustBeRight+    <$> allocateEither rr (fmap Right . alloc) (\a -> free a >> return True)++-- | Generalization of 'allocate' for allocation functions that may fail+allocateEither ::+  forall m e a.+  (MonadSTM m, MonadMask m, MonadThread m, HasCallStack) =>+  ResourceRegistry m ->+  (ResourceId -> m (Either e a)) ->+  -- | Release the resource, return 'True' when the resource+  -- hasn't been released or closed before.+  (a -> m Bool) ->+  m (Either e (ResourceKey m, a))+allocateEither rr alloc free = do+  context <- captureContext+  ensureKnownThread rr context+  -- We check if the registry has been closed when we allocate the key, so+  -- that we can avoid needlessly allocating the resource.+  mKey <- updateState rr allocKey+  case mKey of+    Left closed ->+      throwRegistryClosed rr context closed+    Right key -> mask_ $ do+      ma <- alloc key+      case ma of+        Left e -> return $ Left e+        Right a -> do+          -- TODO: Might want to have an exception handler around this call to+          -- 'updateState' just in case /that/ throws an exception.+          inserted <-+            updateState rr $+              insertResource key (mkResource context a)+          case inserted of+            Left closed -> do+              -- Despite the earlier check, it's possible that the registry+              -- got closed after we allocated a new key but before we got a+              -- chance to register the resource. In this case, we must+              -- deallocate the resource again before throwing the exception.+              void $ free a+              throwRegistryClosed rr context closed+            Right () ->+              return $ Right (ResourceKey rr key, a)+ where+  mkResource :: Context m -> a -> Resource m+  mkResource context a =+    Resource+      { resourceContext = context+      , resourceRelease = Release $ free a+      }++throwRegistryClosed ::+  (MonadThrow m, MonadThread m) =>+  ResourceRegistry m ->+  Context m ->+  PrettyCallStack ->+  m x+throwRegistryClosed rr context closed =+  throwIO+    RegistryClosedException+      { registryClosedRegistryContext = registryContext rr+      , registryClosedCloseCallStack = closed+      , registryClosedAllocContext = context+      }++-- | Release resource+--+-- This deallocates the resource and removes it from the registry. It will be+-- the responsibility of the caller to make sure that the resource is no longer+-- used in any thread.+--+-- The deallocation function is run with exceptions masked, so that we are+-- guaranteed not to remove the resource from the registry without releasing it.+--+-- Releasing an already released resource is a no-op.+--+-- When the resource has not been released before, its context is returned.+release ::+  (MonadMask m, MonadSTM m, MonadThread m, HasCallStack) =>+  ResourceKey m ->+  m (Maybe (Context m))+release key@(ResourceKey rr _) = do+  context <- captureContext+  ensureKnownThread rr context+  unsafeRelease key++-- | Unsafe version of 'release'+--+-- The only difference between 'release' and 'unsafeRelease' is that the latter+-- does not insist that it is called from a thread that is known to the+-- registry. This is dangerous, because it implies that there is a thread with+-- access to a resource which may be deallocated before that thread is+-- terminated. Of course, we can't detect all such situations (when the thread+-- merely uses a resource but does not allocate or release we can't tell), but+-- normally when we /do/ detect this we throw an exception.+--+-- This function should only be used if the above situation can be ruled out+-- or handled by other means.+unsafeRelease ::+  (MonadMask m, MonadSTM m) =>+  ResourceKey m ->+  m (Maybe (Context m))+unsafeRelease (ResourceKey rr rid) = do+  mask_ $ do+    mResource <- updateState rr $ removeResource rid+    case mResource of+      Nothing -> return Nothing+      Just resource -> do+        actuallyReleased <- releaseResource resource+        return $+          if actuallyReleased+            then Just (resourceContext resource)+            else Nothing++-- | Release all resources in the 'ResourceRegistry' without closing.+--+-- See 'closeRegistry' for more details.+releaseAll ::+  (MonadMask m, MonadSTM m, MonadThread m, HasCallStack) =>+  ResourceRegistry m ->+  m ()+releaseAll rr = do+  context <- captureContext+  unless (contextThreadId context == contextThreadId (registryContext rr)) $+    throwIO $+      ResourceRegistryClosedFromWrongThread+        { resourceRegistryCreatedIn = registryContext rr+        , resourceRegistryUsedIn = context+        }+  void $ releaseAllHelper rr context release++-- | This is to 'releaseAll' what 'unsafeRelease' is to 'release': we do not+-- insist that this funciton is called from a thread that is known to the+-- registry. See 'unsafeRelease' for why this is dangerous.+unsafeReleaseAll ::+  (MonadMask m, MonadSTM m, MonadThread m, HasCallStack) =>+  ResourceRegistry m ->+  m ()+unsafeReleaseAll rr = do+  context <- captureContext+  void $ releaseAllHelper rr context unsafeRelease++-- | Internal helper used by 'releaseAll' and 'unsafeReleaseAll'.+releaseAllHelper ::+  (MonadMask m, MonadSTM m, MonadThread m) =>+  ResourceRegistry m ->+  Context m ->+  -- | How to release a resource+  (ResourceKey m -> m (Maybe (Context m))) ->+  m [Context m]+releaseAllHelper rr context releaser = mask_ $ do+  mKeys <- updateState rr $ unlessClosed $ gets getYoungestToOldest+  case mKeys of+    Left closed -> throwRegistryClosed rr context closed+    Right keys -> releaseResources rr keys releaser++{-------------------------------------------------------------------------------+  Threads+-------------------------------------------------------------------------------}++-- | Thread+--+-- The internals of this type are not exported.+data Thread m a = MonadThread m => Thread+  { threadId :: !(ThreadId m)+  -- ^ The underlying @async@ thread id+  , threadResourceId :: !ResourceId+  , threadAsync :: !(Async m a)+  , threadRegistry :: !(ResourceRegistry m)+  }+  deriving NoThunks via OnlyCheckWhnfNamed "Thread" (Thread m a)++-- | 'Eq' instance for 'Thread' compares 'threadId' only.+instance MonadThread m => Eq (Thread m a) where+  Thread{threadId = a} == Thread{threadId = b} = a == b++-- | Cancel a thread+--+-- This is a synchronous operation: the thread will have terminated when this+-- function returns.+--+-- Uses 'uninterruptibleCancel' because that's what 'withAsync' does.+cancelThread :: MonadAsync m => Thread m a -> m ()+cancelThread = uninterruptibleCancel . threadAsync++-- | Wait for thread to terminate and return its result.+--+-- If the thread throws an exception, this will rethrow that exception.+--+-- NOTE: If A waits on B, and B is linked to the registry, and B throws an+-- exception, then A might /either/ receive the exception thrown by B /or/+-- the 'Control.Exception.ThreadKilled' exception thrown by the registry.+waitThread :: MonadAsync m => Thread m a -> m a+waitThread = wait . threadAsync++-- | Lift 'waitAny' to 'Thread'+waitAnyThread :: forall m a. MonadAsync m => [Thread m a] -> m a+waitAnyThread ts = snd <$> waitAny (map threadAsync ts)++-- | Fork a new thread+forkThread ::+  forall m a.+  (MonadMask m, MonadAsync m, HasCallStack) =>+  ResourceRegistry m ->+  -- | Label for the thread+  String ->+  m a ->+  m (Thread m a)+forkThread rr label body =+  snd+    <$> allocate rr (\key -> mkThread key <$> async (body' key)) cancelThread+ where+  mkThread :: ResourceId -> Async m a -> Thread m a+  mkThread rid child =+    Thread+      { threadId = asyncThreadId child+      , threadResourceId = rid+      , threadAsync = child+      , threadRegistry = rr+      }++  body' :: ResourceId -> m a+  body' rid = do+    me <- myThreadId+    labelThread me label+    (registerThread me >> body) `finally` unregisterThread me rid++  -- Register the thread+  --+  -- We must add the thread to the list of known threads before the thread+  -- will start to use the registry.+  registerThread :: ThreadId m -> m ()+  registerThread tid = updateState rr $ insertThread tid++  -- Unregister the thread+  --+  -- Threads are the only kinds of resources that "deallocate themselves".+  -- We remove the thread from the resources as well as the set of known+  -- threads, so that these datastructures do not grow without bound.+  --+  -- This runs with asynchronous exceptions masked (due to 'finally'),+  -- though for the current implementation of 'unregisterThread' this+  -- makes no difference.+  unregisterThread :: ThreadId m -> ResourceId -> m ()+  unregisterThread tid rid =+    updateState rr $ do+      removeThread tid+      void $ removeResource rid++-- | Bracketed version of 'forkThread'+--+-- The analogue of 'withAsync' for the registry.+--+-- Scoping thread lifetime using 'withThread' is important when a parent+-- thread wants to link to a child thread /and handle any exceptions arising+-- from the link/:+--+-- > let handleLinkException :: ExceptionInLinkedThread -> m ()+-- >     handleLinkException = ..+-- > in handle handleLinkException $+-- >      withThread registry codeInChild $ \child ->+-- >        ..+--+-- instead of+--+-- > handle handleLinkException $ do  -- PROBABLY NOT CORRECT!+-- >   child <- forkThread registry codeInChild+-- >   ..+--+-- where the parent may exit the scope of the exception handler before the child+-- terminates. If the lifetime of the child cannot be limited to the lifetime of+-- the parent, the child should probably be linked to the registry instead and+-- the thread that spawned the registry should handle any exceptions.+--+-- Note that in /principle/ there is no problem in using 'withAsync' alongside a+-- registry. After all, in a pattern like+--+-- > withRegistry $ \registry ->+-- >   ..+-- >   withAsync (.. registry ..) $ \async ->+-- >     ..+--+-- the async will be cancelled when leaving the scope of 'withAsync' and so+-- that reference to the registry, or indeed any of the resources inside the+-- registry, is safe. However, the registry implements a sanity check that the+-- registry is only used from known threads. This is useful: when a thread that+-- is not known to the registry (in other words, whose lifetime is not tied to+-- the lifetime of the registry) spawns a resource in that registry, that+-- resource may well be deallocated before the thread terminates, leading to+-- undefined and hard to debug behaviour (indeed, whether or not this results in+-- problems may well depend on precise timing); an exception that is thrown when+-- /allocating/ the resource is (more) deterministic and easier to debug.+-- Unfortunately, it means that the above pattern is not applicable, as the+-- thread spawned by 'withAsync' is not known to the registry, and so if it were+-- to try to use the registry, the registry would throw an error (even though+-- this pattern is actually safe). This situation is not ideal, but for now we+-- merely provide an alternative to 'withAsync' that /does/ register the thread+-- with the registry.+--+-- NOTE: Threads that are spawned out of the user's control but that must still+-- make use of the registry can use the unsafe API. This should be used with+-- caution, however.+withThread ::+  (MonadMask m, MonadAsync m) =>+  ResourceRegistry m ->+  -- | Label for the thread+  String ->+  m a ->+  (Thread m a -> m b) ->+  m b+withThread rr label body = bracket (forkThread rr label body) cancelThread++-- | Link specified 'Thread' to the (thread that created) the registry+linkToRegistry :: (MonadAsync m, MonadFork m, MonadMask m) => Thread m a -> m ()+linkToRegistry t = linkTo (registryThread $ threadRegistry t) (threadAsync t)++-- | Fork a thread and link to it to the registry.+--+-- This function is just a convenience.+forkLinkedThread ::+  (MonadAsync m, MonadFork m, MonadMask m, HasCallStack) =>+  ResourceRegistry m ->+  -- | Label for the thread+  String ->+  m a ->+  m (Thread m a)+forkLinkedThread rr label body = do+  t <- forkThread rr label body+  -- There is no race condition here between the new thread throwing an+  -- exception and the 'linkToRegistry': if the thread /already/ threw the+  -- exception when we link it, the exception will be raised immediately+  -- (see 'linkTo' for details).+  linkToRegistry t+  return t++{-------------------------------------------------------------------------------+  Check that registry is used from known thread+-------------------------------------------------------------------------------}++ensureKnownThread ::+  forall m.+  (MonadThrow m, MonadThread m, MonadSTM m) =>+  ResourceRegistry m ->+  Context m ->+  m ()+ensureKnownThread rr context = do+  isKnown <- checkIsKnown+  unless isKnown $+    throwIO $+      ResourceRegistryUsedFromUntrackedThread+        { resourceRegistryCreatedIn = registryContext rr+        , resourceRegistryUsedIn = context+        }+ where+  checkIsKnown :: m Bool+  checkIsKnown+    | contextThreadId context == contextThreadId (registryContext rr) =+        return True+    | otherwise = atomically $ do+        KnownThreads ts <- registryThreads <$> readTVar (registryState rr)+        return $ contextThreadId context `Set.member` ts++-- | Registry used from untracked threads+--+-- If this exception is raised, it indicates a bug in the caller.+data ResourceRegistryThreadException+  = -- | If the registry is used from an untracked thread, we cannot do proper+    -- reference counting. The following threads are /tracked/: the thread+    -- that spawned the registry and all threads spawned by the registry.+    forall m. MonadThread m => ResourceRegistryUsedFromUntrackedThread+      { resourceRegistryCreatedIn :: !(Context m)+      -- ^ Information about the context in which the registry was created+      , resourceRegistryUsedIn :: !(Context m)+      -- ^ The context in which it was used+      }+  | -- | Registry closed from different threat than that created it+    forall m. MonadThread m => ResourceRegistryClosedFromWrongThread+      { resourceRegistryCreatedIn :: !(Context m)+      -- ^ Information about the context in which the registry was created+      , resourceRegistryUsedIn :: !(Context m)+      -- ^ The context in which it was used+      }++deriving instance Show ResourceRegistryThreadException+instance Exception ResourceRegistryThreadException++{-------------------------------------------------------------------------------+  Auxiliary: context+-------------------------------------------------------------------------------}++-- | The internal context of a resource registry, recording a 'PrettyCallStack'+-- of its creation and the creator's 'ThreadId'+data Context m = MonadThread m => Context+  { contextCallStack :: !PrettyCallStack+  -- ^ CallStack in which it was created+  , contextThreadId :: !(ThreadId m)+  -- ^ Thread that created the registry or resource+  }++-- Existential type; we can't use generics+instance NoThunks (Context m) where+  showTypeOf _ = "Context"+  wNoThunks ctxt (Context cs tid) =+    allNoThunks+      [ noThunks ctxt cs+      , noThunks ctxt (InspectHeapNamed @"ThreadId" tid)+      ]++deriving instance Show (Context m)++captureContext :: MonadThread m => HasCallStack => m (Context m)+captureContext = Context prettyCallStack <$> myThreadId++{-------------------------------------------------------------------------------+  Misc utilities+-------------------------------------------------------------------------------}++-- | Generalization of 'link' that links an async to an arbitrary thread.+--+-- Non standard (not in 'async' library)+linkTo ::+  (MonadAsync m, MonadFork m, MonadMask m) =>+  ThreadId m ->+  Async m a ->+  m ()+linkTo tid = linkToOnly tid (not . isCancel)++-- | Generalization of 'linkOnly' that links an async to an arbitrary thread.+--+-- Non standard (not in 'async' library).+linkToOnly ::+  forall m a.+  (MonadAsync m, MonadFork m, MonadMask m) =>+  ThreadId m ->+  (SomeException -> Bool) ->+  Async m a ->+  m ()+linkToOnly tid shouldThrow a = do+  void $ forkRepeat ("linkToOnly " <> show linkedThreadId) $ do+    r <- waitCatch a+    case r of+      Left e | shouldThrow e -> throwTo tid (exceptionInLinkedThread e)+      _otherwise -> return ()+ where+  linkedThreadId :: ThreadId m+  linkedThreadId = asyncThreadId a++  exceptionInLinkedThread :: SomeException -> ExceptionInLinkedThread+  exceptionInLinkedThread =+    ExceptionInLinkedThread (show linkedThreadId)++isCancel :: SomeException -> Bool+isCancel e+  | Just AsyncCancelled <- fromException e = True+  | otherwise = False++forkRepeat :: (MonadFork m, MonadMask m) => String -> m a -> m (ThreadId m)+forkRepeat label action =+  mask $ \restore ->+    let go = do+          r <- tryAll (restore action)+          case r of+            Left _ -> go+            _ -> return ()+     in forkIO (labelThisThread label >> go)++tryAll :: MonadCatch m => m a -> m (Either SomeException a)+tryAll = try++mustBeRight :: Either Void a -> a+mustBeRight (Left v) = absurd v+mustBeRight (Right a) = a++{-------------------------------------------------------------------------------+  Auxiliary: CallStack with different Show instance+-------------------------------------------------------------------------------}++-- | CallStack with 'Show' instance using 'prettyCallStack'+newtype PrettyCallStack = PrettyCallStack CallStack+  deriving newtype NoThunks++instance Show PrettyCallStack where+  show (PrettyCallStack cs) = GHC.prettyCallStack cs++-- | Capture a 'PrettyCallStack'+prettyCallStack :: HasCallStack => PrettyCallStack+prettyCallStack = PrettyCallStack GHC.callStack++{-------------------------------------------------------------------------------+  Orphan instance+-------------------------------------------------------------------------------}++instance+  (NoThunks k, NoThunks v) =>+  NoThunks (Bimap k v)+  where   wNoThunks ctxt = noThunksInKeysAndValues ctxt . Bimap.toList
test/Main.hs view
@@ -47,9 +47,8 @@ import Data.Map.Strict qualified as Map import Data.TreeDiff import Data.Typeable-import Generics.SOP qualified as SOP import GHC.Generics (Generic, Generic1)-import Prelude+import Generics.SOP qualified as SOP import Test.QuickCheck hiding (forAll) import Test.QuickCheck.Monadic hiding (run) import Test.StateMachine@@ -60,12 +59,15 @@ import Test.Util.QSM import Test.Util.SOP import Test.Util.ToExpr ()+import Prelude  main :: IO ()-main = defaultMain-    $ testGroup "ResourceRegistry" [-      testProperty "sequential" prop_sequential-    ]+main =+  defaultMain $+    testGroup+      "ResourceRegistry"+      [ testProperty "sequential" prop_sequential+      ]  {-------------------------------------------------------------------------------   Mock implementaton@@ -85,14 +87,14 @@ -- -- Once created, threads are never removed from this map. Instead, when they are -- killed their 'alive' status is set to 'False'.-newtype MockThreads = MTs { mockThreadsMap :: Map Int MockState }+newtype MockThreads = MTs {mockThreadsMap :: Map Int MockState}   deriving (Show, Generic)  -- | State of a mock thread-data MockState = MS {-      alive :: Bool-    , kids  :: MockThreads-    }+data MockState = MS+  { alive :: Bool+  , kids :: MockThreads+  }   deriving (Show, Generic)  -- | All known threads, and whether or not they are alive@@ -102,69 +104,75 @@ -- computing that here. mockThreads :: MockThreads -> [(MockThread, Bool)] mockThreads = go [] True-  where-    go :: [Int] -> Bool -> MockThreads -> [(MockThread, Bool)]-    go prefix parentAlive =-        concatMap aux . Map.toList . mockThreadsMap-      where-        aux :: (Int, MockState) -> [(MockThread, Bool)]-        aux (tid, MS{..}) =-            (t, parentAlive && alive) : go t alive' kids-          where-            t :: [Int]-            t = prefix ++ [tid]+ where+  go :: [Int] -> Bool -> MockThreads -> [(MockThread, Bool)]+  go prefix parentAlive =+    concatMap aux . Map.toList . mockThreadsMap+   where+    aux :: (Int, MockState) -> [(MockThread, Bool)]+    aux (tid, MS{..}) =+      (t, parentAlive && alive) : go t alive' kids+     where+      t :: [Int]+      t = prefix ++ [tid] -            alive' :: Bool-            alive' = parentAlive && alive+      alive' :: Bool+      alive' = parentAlive && alive  mockLiveThreads :: MockThreads -> [MockThread] mockLiveThreads = map fst . filter snd . mockThreads -alterThreadF :: forall m. MonadError Err m-             => MockThread-             -> (Maybe MockState -> m MockState)-             -> MockThreads -> m MockThreads+alterThreadF ::+  forall m.+  MonadError Err m =>+  MockThread ->+  (Maybe MockState -> m MockState) ->+  MockThreads ->+  m MockThreads alterThreadF [] _ _ =-    error "alterThreadF: invalid thread"+  error "alterThreadF: invalid thread" alterThreadF [t] f (MTs m) =-    MTs <$> Map.alterF (fmap Just . f) t m-alterThreadF thread@(t:ts) f (MTs m) =-    MTs <$> Map.alterF (fmap Just . f') t m-  where-    f' :: Maybe MockState -> m MockState-    f' Nothing   = throwError $ ErrInvalidThread (show thread)-    f' (Just ms) = (\kids' -> ms { kids = kids' }) <$>-                     alterThreadF ts f (kids ms)+  MTs <$> Map.alterF (fmap Just . f) t m+alterThreadF thread@(t : ts) f (MTs m) =+  MTs <$> Map.alterF (fmap Just . f') t m+ where+  f' :: Maybe MockState -> m MockState+  f' Nothing = throwError $ ErrInvalidThread (show thread)+  f' (Just ms) =+    (\kids' -> ms{kids = kids'})+      <$> alterThreadF ts f (kids ms)  -- Create thread with the given ID mockFork :: MockThread -> MockThreads -> Except Err MockThreads mockFork t = alterThreadF t $ \case-    Just _  -> error "fork: thread already exists (bug in runMock)"-    Nothing -> return newState-  where-    newState :: MockState-    newState = MS {-          alive = True-        , kids  = MTs Map.empty-        }+  Just _ -> error "fork: thread already exists (bug in runMock)"+  Nothing -> return newState+ where+  newState :: MockState+  newState =+    MS+      { alive = True+      , kids = MTs Map.empty+      }  mockKill :: MockThread -> MockThreads -> Except Err MockThreads mockKill t = alterThreadF t $ \case-    Nothing -> throwError $ ErrInvalidThread (show t)-    Just st -> return st { alive = False }+  Nothing -> throwError $ ErrInvalidThread (show t)+  Just st -> return st{alive = False} -data Mock = Mock {-      nextId  :: Int-    , threads :: MockThreads-    , links   :: Map MockThread (Link MockThread)-    }+data Mock = Mock+  { nextId :: Int+  , threads :: MockThreads+  , links :: Map MockThread (Link MockThread)+  }   deriving (Show, Generic)  emptyMock :: Mock-emptyMock = Mock {-      nextId  = 1+emptyMock =+  Mock+    { nextId = 1     , threads = MTs Map.empty-    , links   = Map.empty+    , links = Map.empty     }  {-------------------------------------------------------------------------------@@ -177,34 +185,30 @@ data Link a = LinkFromParent a | DontLink   deriving (Show, Functor, Generic) -data Cmd t =-    -- | Fork a new top-level thread+data Cmd t+  = -- | Fork a new top-level thread     --     -- We don't allow linking here, because we don't want an exception in one     -- of these threads to kill the thread running the tests.     Fork--    -- | Fork a child thread-  | ForkFrom t (Link ())--    -- | Cause a thread to terminate normally-  | Terminate t--    -- | Cause a thread to terminate abnormally-  | Crash t--    -- | Get all live threads-  | LiveThreads+  | -- | Fork a child thread+    ForkFrom t (Link ())+  | -- | Cause a thread to terminate normally+    Terminate t+  | -- | Cause a thread to terminate abnormally+    Crash t+  | -- | Get all live threads+    LiveThreads   deriving (Show, Functor, Foldable, Traversable, Generic) -data Success t =-    Unit ()+data Success t+  = Unit ()   | Spawned t   | Threads [t]   deriving (Show, Eq, Functor, Foldable, Traversable) -data Err =-    ErrTimeout+data Err+  = ErrTimeout   | ErrInvalidThread String   deriving (Show, Eq) @@ -219,11 +223,11 @@  normalize :: Resp MockThread -> Resp MockThread normalize (Resp r) = Resp $ aux <$> r-  where-    aux :: Success MockThread -> Success MockThread-    aux (Unit ())    = Unit ()-    aux (Spawned t)  = Spawned t-    aux (Threads ts) = Threads (sort ts)+ where+  aux :: Success MockThread -> Success MockThread+  aux (Unit ()) = Unit ()+  aux (Spawned t) = Spawned t+  aux (Threads ts) = Threads (sort ts)  {-------------------------------------------------------------------------------   Run against the mock implementation@@ -231,51 +235,52 @@  runMock :: Cmd MockThread -> Mock -> (Resp MockThread, Mock) runMock cmd m@Mock{..} =-    case runExcept (go cmd) of-      Left  err           -> (Resp (Left err), m)-      Right (success, m') -> (Resp (Right success), m')-  where-    go :: Cmd MockThread -> Except Err (Success MockThread, Mock)-    go Fork                = createThread DontLink           [nextId]-    go (ForkFrom t linked) = createThread (const t <$> linked) (t ++ [nextId])-    go (Terminate t)       = (\x -> (Unit (), m { threads = x })) <$> mockKill t threads-    go (Crash t)           = (\x -> (Unit (), m { threads = x })) <$> killAll  t threads-    go LiveThreads         = return (Threads $ mockLiveThreads threads, m)+  case runExcept (go cmd) of+    Left err -> (Resp (Left err), m)+    Right (success, m') -> (Resp (Right success), m')+ where+  go :: Cmd MockThread -> Except Err (Success MockThread, Mock)+  go Fork = createThread DontLink [nextId]+  go (ForkFrom t linked) = createThread (const t <$> linked) (t ++ [nextId])+  go (Terminate t) = (\x -> (Unit (), m{threads = x})) <$> mockKill t threads+  go (Crash t) = (\x -> (Unit (), m{threads = x})) <$> killAll t threads+  go LiveThreads = return (Threads $ mockLiveThreads threads, m) -    createThread :: Link MockThread -- Thread to link to (if any)-                 -> MockThread -> Except Err (Success MockThread, Mock)-    createThread shouldLink t = do-        threads' <- mockFork t threads-        return (-            Spawned t-          , m { nextId  = succ nextId-              , threads = threads'-              , links   = Map.insert t shouldLink links-              }-          )+  createThread ::+    Link MockThread -> -- Thread to link to (if any)+    MockThread ->+    Except Err (Success MockThread, Mock)+  createThread shouldLink t = do+    threads' <- mockFork t threads+    return+      ( Spawned t+      , m+          { nextId = succ nextId+          , threads = threads'+          , links = Map.insert t shouldLink links+          }+      ) -    killAll :: MockThread -> MockThreads -> Except Err MockThreads-    killAll t =-        mockKill t >=> killParent (Map.findWithDefault DontLink t links)-      where-        killParent :: Link MockThread -> MockThreads -> Except Err MockThreads-        killParent DontLink            = return-        killParent (LinkFromParent t') = killAll t'+  killAll :: MockThread -> MockThreads -> Except Err MockThreads+  killAll t =+    mockKill t >=> killParent (Map.findWithDefault DontLink t links)+   where+    killParent :: Link MockThread -> MockThreads -> Except Err MockThreads+    killParent DontLink = return+    killParent (LinkFromParent t') = killAll t'  {-------------------------------------------------------------------------------   Run in IO (possibly simulated) -------------------------------------------------------------------------------} -data TestThread m = TestThread {-      -- | The underlying 'Thread'-      testThread   :: Thread m ()--      -- | Parent thread this thread is linked to (if any)-    , threadLinked :: Link (TestThread m)--      -- | Send the thread instructions (see 'ThreadInstr')-    , threadComms  :: StrictTQueue m (QueuedInstr m)-    }+data TestThread m = TestThread+  { testThread :: Thread m ()+  -- ^ The underlying 'Thread'+  , threadLinked :: Link (TestThread m)+  -- ^ Parent thread this thread is linked to (if any)+  , threadComms :: StrictTQueue m (QueuedInstr m)+  -- ^ Send the thread instructions (see 'ThreadInstr')+  }  -- | Instructions to a thread --@@ -283,10 +288,8 @@ data ThreadInstr m :: Type -> Type where   -- | Have the thread spawn a child thread   ThreadFork :: Link () -> ThreadInstr m (TestThread m)-   -- | Have the thread terminate normally   ThreadTerminate :: ThreadInstr m ()-   -- | Raise an exception in the thread   ThreadCrash :: ThreadInstr m () @@ -295,14 +298,14 @@  runInThread :: (MonadMVar m, MonadSTM m) => TestThread m -> ThreadInstr m a -> m a runInThread TestThread{..} instr = do-    result <- newEmptyMVar-    atomically $ writeTQueue threadComms (QueuedInstr instr result)-    takeMVar result+  result <- newEmptyMVar+  atomically $ writeTQueue threadComms (QueuedInstr instr result)+  takeMVar result -instance (MonadThread m) => Show (TestThread m) where+instance MonadThread m => Show (TestThread m) where   show TestThread{..} = "<Thread " ++ show (threadId testThread) ++ ">" -instance (MonadThread m) => Eq (TestThread m) where+instance MonadThread m => Eq (TestThread m) where   (==) = (==) `on` (threadId . testThread)  -- | Create a new thread in the given registry@@ -310,61 +313,69 @@ -- In order to be able to see which threads are alive, we have threads -- register and unregister themselves. We do not reuse the registry for this, -- to avoid circular reasoning in the tests.-newThread :: forall m. (MonadMVar m, MonadMask m, MonadAsync m, MonadFork m)-          => StrictTVar m [TestThread m]-          -> ResourceRegistry m-          -> Link (TestThread m)-          -> m (TestThread m)+newThread ::+  forall m.+  (MonadMVar m, MonadMask m, MonadAsync m, MonadFork m) =>+  StrictTVar m [TestThread m] ->+  ResourceRegistry m ->+  Link (TestThread m) ->+  m (TestThread m) newThread alive parentReg = \shouldLink -> do-    comms      <- atomically $ newTQueue-    spawned    <- newEmptyMVar+  comms <- atomically $ newTQueue+  spawned <- newEmptyMVar -    thread <- forkThread parentReg "newThread" $-                withRegistry $ \childReg ->-                  threadBody childReg spawned comms-    case shouldLink of-      LinkFromParent _ -> linkToRegistry thread-      DontLink         -> return ()+  thread <- forkThread parentReg "newThread" $+    withRegistry $ \childReg ->+      threadBody childReg spawned comms+  case shouldLink of+    LinkFromParent _ -> linkToRegistry thread+    DontLink -> return () -    let testThread :: TestThread m-        testThread = TestThread {-                         testThread   = thread-                       , threadLinked = shouldLink-                       , threadComms  = comms-                       }+  let testThread :: TestThread m+      testThread =+        TestThread+          { testThread = thread+          , threadLinked = shouldLink+          , threadComms = comms+          } -    -- Make sure to register thread before starting it-    atomically $ modifyTVar alive (testThread:)-    putMVar spawned testThread-    return testThread-  where-    threadBody :: ResourceRegistry m-               -> StrictMVar m (TestThread m)-               -> StrictTQueue m (QueuedInstr m)-               -> m ()-    threadBody childReg spawned comms = do-        us <- readMVar spawned-        loop us `finally` (atomically $ modifyTVar alive (delete us))-      where-        loop :: TestThread m -> m ()-        loop us = do-          QueuedInstr instr result <- atomically $ readTQueue comms-          case instr of-            ThreadFork linked -> do-              child <- newThread alive childReg (const us <$> linked)-              putMVar result child-              loop us-            ThreadTerminate -> do-              putMVar result ()-            ThreadCrash -> do-              putMVar result ()-              error "crashing"+  -- Make sure to register thread before starting it+  atomically $ modifyTVar alive (testThread :)+  putMVar spawned testThread+  return testThread+ where+  threadBody ::+    ResourceRegistry m ->+    StrictMVar m (TestThread m) ->+    StrictTQueue m (QueuedInstr m) ->+    m ()+  threadBody childReg spawned comms = do+    us <- readMVar spawned+    loop us `finally` (atomically $ modifyTVar alive (delete us))+   where+    loop :: TestThread m -> m ()+    loop us = do+      QueuedInstr instr result <- atomically $ readTQueue comms+      case instr of+        ThreadFork linked -> do+          child <- newThread alive childReg (const us <$> linked)+          putMVar result child+          loop us+        ThreadTerminate -> do+          putMVar result ()+        ThreadCrash -> do+          putMVar result ()+          error "crashing" -runIO :: forall m. (MonadMVar m, MonadTimer m, MonadMask m, MonadAsync m, MonadFork m)-      => StrictTVar m [TestThread m]-      -> ResourceRegistry m-      -> Cmd (TestThread m) -> m (Resp (TestThread m))-runIO alive reg cmd = catchEx $ timeout 1 $+runIO ::+  forall m.+  (MonadMVar m, MonadTimer m, MonadMask m, MonadAsync m, MonadFork m) =>+  StrictTVar m [TestThread m] ->+  ResourceRegistry m ->+  Cmd (TestThread m) ->+  m (Resp (TestThread m))+runIO alive reg cmd = catchEx $+  timeout 1 $     case cmd of       Fork ->         Spawned <$> newThread alive reg DontLink@@ -378,19 +389,19 @@         Unit <$> waitForTermination thread       LiveThreads ->         atomically $ Threads <$> readTVar alive-  where-    catchEx :: m (Maybe (Success a)) -> m (Resp a)-    catchEx = fmap (Resp . maybe (Left ErrTimeout) Right)+ where+  catchEx :: m (Maybe (Success a)) -> m (Resp a)+  catchEx = fmap (Resp . maybe (Left ErrTimeout) Right) -    -- For the thread and all of its linked parents to have terminated-    waitForTermination :: TestThread m -> m ()-    waitForTermination t = do-        result <- try $ waitThread (testThread t)-        case (result, threadLinked t) of-          (Left (_ :: SomeException), LinkFromParent t') ->-            waitForTermination t'-          _otherwise ->-            return ()+  -- For the thread and all of its linked parents to have terminated+  waitForTermination :: TestThread m -> m ()+  waitForTermination t = do+    result <- try $ waitThread (testThread t)+    case (result, threadLinked t) of+      (Left (_ :: SomeException), LinkFromParent t') ->+        waitForTermination t'+      _otherwise ->+        return ()  {-------------------------------------------------------------------------------   QSM wrappers@@ -398,7 +409,7 @@  newtype At m f r = At (f (Reference (TestThread m) r)) -deriving instance (MonadThread m, Show1 r) => Show (At m Cmd  r)+deriving instance (MonadThread m, Show1 r) => Show (At m Cmd r) deriving instance (MonadThread m, Show1 r) => Show (At m Resp r)  {-------------------------------------------------------------------------------@@ -410,8 +421,8 @@  (!) :: (Eq k, Show k) => [(k, a)] -> k -> a env ! r = case lookup r env of-            Just a  -> a-            Nothing -> error $ "Unknown reference: " ++ show r+  Just a -> a+  Nothing -> error $ "Unknown reference: " ++ show r  data Model m r = Model Mock (Refs m r)   deriving (Show, Generic)@@ -423,72 +434,81 @@   Events -------------------------------------------------------------------------------} -toMock :: forall m f r. (Functor f, Eq1 r, Show1 r, MonadThread m)-       => Model m r -> At m f r -> f MockThread+toMock ::+  forall m f r.+  (Functor f, Eq1 r, Show1 r, MonadThread m) =>+  Model m r -> At m f r -> f MockThread toMock (Model _ hs) (At fr) = (hs !) <$> fr -step :: (Eq1 r, Show1 r, MonadThread m)-     => Model m r -> At m Cmd r -> (Resp MockThread, Mock)+step ::+  (Eq1 r, Show1 r, MonadThread m) =>+  Model m r -> At m Cmd r -> (Resp MockThread, Mock) step m@(Model mock _) c = runMock (toMock m c) mock -data Event m r = Event {-      before   :: Model  m     r-    , cmd      :: At     m Cmd r-    , after    :: Model  m     r-    , mockResp :: Resp MockThread-    }+data Event m r = Event+  { before :: Model m r+  , cmd :: At m Cmd r+  , after :: Model m r+  , mockResp :: Resp MockThread+  } -lockstep :: (Eq1 r, Show1 r, MonadThread m)-         => Model m      r-         -> At    m Cmd  r-         -> At    m Resp r-         -> Event m      r-lockstep m@(Model _ hs) c (At resp) = Event {-      before   = m-    , cmd      = c-    , after    = Model mock' (hs <> hs')+lockstep ::+  (Eq1 r, Show1 r, MonadThread m) =>+  Model m r ->+  At m Cmd r ->+  At m Resp r ->+  Event m r+lockstep m@(Model _ hs) c (At resp) =+  Event+    { before = m+    , cmd = c+    , after = Model mock' (hs <> hs')     , mockResp = resp'     }-  where-    (resp', mock') = step m c-    hs' = zip (newHandles resp) (newHandles resp')+ where+  (resp', mock') = step m c+  hs' = zip (newHandles resp) (newHandles resp') -    newHandles :: Resp r -> [r]-    newHandles (Resp (Left _))            = []-    newHandles (Resp (Right (Unit ())))   = []-    newHandles (Resp (Right (Spawned t))) = [t]-    newHandles (Resp (Right (Threads _))) = []+  newHandles :: Resp r -> [r]+  newHandles (Resp (Left _)) = []+  newHandles (Resp (Right (Unit ()))) = []+  newHandles (Resp (Right (Spawned t))) = [t]+  newHandles (Resp (Right (Threads _))) = []  {-------------------------------------------------------------------------------   Generator -------------------------------------------------------------------------------}  generator :: forall m. Model m Symbolic -> Maybe (Gen (At m Cmd Symbolic))-generator (Model _ hs) = Just $ oneof $ concat [-      withoutHandle-    , if null hs then [] else withHandle (elements (map fst hs))-    ]-  where-    withoutHandle :: [Gen (At m Cmd Symbolic)]-    withoutHandle = [-          fmap At $ return Fork-        , fmap At $ return LiveThreads+generator (Model _ hs) =+  Just $+    oneof $+      concat+        [ withoutHandle+        , if null hs then [] else withHandle (elements (map fst hs))         ]+ where+  withoutHandle :: [Gen (At m Cmd Symbolic)]+  withoutHandle =+    [ fmap At $ return Fork+    , fmap At $ return LiveThreads+    ] -    withHandle :: Gen (Reference (TestThread m) Symbolic)-               -> [Gen (At m Cmd Symbolic)]-    withHandle pickThread = [-          fmap At $ Terminate <$> pickThread-        , fmap At $ Crash     <$> pickThread-        , fmap At $ ForkFrom  <$> pickThread <*> genLink-        ]+  withHandle ::+    Gen (Reference (TestThread m) Symbolic) ->+    [Gen (At m Cmd Symbolic)]+  withHandle pickThread =+    [ fmap At $ Terminate <$> pickThread+    , fmap At $ Crash <$> pickThread+    , fmap At $ ForkFrom <$> pickThread <*> genLink+    ] -    genLink :: Gen (Link ())-    genLink = aux <$> arbitrary-      where-        aux :: Bool -> Link ()-        aux True  = LinkFromParent ()-        aux False = DontLink+  genLink :: Gen (Link ())+  genLink = aux <$> arbitrary+   where+    aux :: Bool -> Link ()+    aux True = LinkFromParent ()+    aux False = DontLink  shrinker :: Model m Symbolic -> At m Cmd Symbolic -> [At m Cmd Symbolic] shrinker _ _ = []@@ -497,18 +517,18 @@   QSM required instances -------------------------------------------------------------------------------} -instance SOP.Generic         (Cmd t)+instance SOP.Generic (Cmd t) instance SOP.HasDatatypeInfo (Cmd t)  deriving instance Generic1 (At m Cmd) deriving instance Generic1 (At m Resp)  instance CommandNames (At m Cmd) where-  cmdName  (At cmd) = constrName cmd-  cmdNames _        = constrNames (Proxy @(Cmd ()))+  cmdName (At cmd) = constrName cmd+  cmdNames _ = constrNames (Proxy @(Cmd ())) -instance Rank2.Foldable    (At m Cmd)-instance Rank2.Functor     (At m Cmd)+instance Rank2.Foldable (At m Cmd)+instance Rank2.Functor (At m Cmd) instance Rank2.Traversable (At m Cmd)  instance Rank2.Foldable (At m Resp)@@ -519,69 +539,78 @@ instance ToExpr (Link MockThread) instance ToExpr (Model IO Concrete) -instance (MonadThread m) => ToExpr (TestThread m) where+instance MonadThread m => ToExpr (TestThread m) where   toExpr = defaultExprViaShow  {-------------------------------------------------------------------------------   QSM toplevel -------------------------------------------------------------------------------} -semantics :: (MonadMVar m, MonadMask m, MonadAsync m, MonadFork m, MonadTimer m, Typeable m)-          => StrictTVar m [TestThread m]-          -> ResourceRegistry m-          -> At m Cmd Concrete -> m (At m Resp Concrete)+semantics ::+  (MonadMVar m, MonadMask m, MonadAsync m, MonadFork m, MonadTimer m, Typeable m) =>+  StrictTVar m [TestThread m] ->+  ResourceRegistry m ->+  At m Cmd Concrete ->+  m (At m Resp Concrete) semantics alive reg (At c) =-    (At . fmap reference) <$>-      runIO alive reg (concrete <$> c)+  (At . fmap reference)+    <$> runIO alive reg (concrete <$> c) -transition :: (Eq1 r, Show1 r, MonadThread m)-           => Model m r -> At m Cmd r -> At m Resp r -> Model m r+transition ::+  (Eq1 r, Show1 r, MonadThread m) =>+  Model m r -> At m Cmd r -> At m Resp r -> Model m r transition m c = after . lockstep m c -precondition :: forall m. (MonadThread m)-             => Model m Symbolic -> At m Cmd Symbolic -> Logic+precondition ::+  forall m.+  MonadThread m =>+  Model m Symbolic -> At m Cmd Symbolic -> Logic precondition (Model mock hs) (At c) =-    forAll (toList c) checkRef-  where-    checkRef :: Reference (TestThread m) Symbolic -> Logic-    checkRef r =-        case lookup r hs of-          Nothing -> Bot-          Just r' -> r' `member` mockLiveThreads (threads mock)+  forAll (toList c) checkRef+ where+  checkRef :: Reference (TestThread m) Symbolic -> Logic+  checkRef r =+    case lookup r hs of+      Nothing -> Bot+      Just r' -> r' `member` mockLiveThreads (threads mock) -postcondition :: (MonadThread m)-              => Model m      Concrete-              -> At    m Cmd  Concrete-              -> At    m Resp Concrete-              -> Logic+postcondition ::+  MonadThread m =>+  Model m Concrete ->+  At m Cmd Concrete ->+  At m Resp Concrete ->+  Logic postcondition m c r =-    normalize (toMock (after e) r) .== normalize (mockResp e)-  where-    e = lockstep m c r+  normalize (toMock (after e) r) .== normalize (mockResp e)+ where+  e = lockstep m c r -symbolicResp :: (MonadThread m, Typeable m)-             => Model m     Symbolic-             -> At    m Cmd Symbolic-             -> GenSym (At m Resp Symbolic)+symbolicResp ::+  (MonadThread m, Typeable m) =>+  Model m Symbolic ->+  At m Cmd Symbolic ->+  GenSym (At m Resp Symbolic) symbolicResp m c = At <$> traverse (const genSym) resp-  where-    (resp, _mock') = step m c+ where+  (resp, _mock') = step m c -sm :: (MonadMVar m, MonadMask m, MonadAsync m, MonadFork m, MonadTimer m, Typeable m)-   => StrictTVar m [TestThread m]-   -> ResourceRegistry m-   -> StateMachine (Model m) (At m Cmd) m (At m Resp)-sm alive reg = StateMachine {-      initModel     = initModel-    , transition    = transition-    , precondition  = precondition+sm ::+  (MonadMVar m, MonadMask m, MonadAsync m, MonadFork m, MonadTimer m, Typeable m) =>+  StrictTVar m [TestThread m] ->+  ResourceRegistry m ->+  StateMachine (Model m) (At m Cmd) m (At m Resp)+sm alive reg =+  StateMachine+    { initModel = initModel+    , transition = transition+    , precondition = precondition     , postcondition = postcondition-    , invariant     = Nothing-    , generator     = generator-    , shrinker      = shrinker-    , semantics     = semantics alive reg-    , mock          = symbolicResp-    , cleanup       = noCleanup+    , invariant = Nothing+    , generator = generator+    , shrinker = shrinker+    , semantics = semantics alive reg+    , mock = symbolicResp+    , cleanup = noCleanup     }  prop_sequential :: Property@@ -589,13 +618,13 @@  prop_sequential' :: QSM.Commands (At IO Cmd) (At IO Resp) -> Property prop_sequential' cmds = monadicIO $ do-    alive <- liftIO $ newTVarIO []-    reg   <- liftIO $ unsafeNewRegistry-    let sm' = sm alive reg-    (hist, _model, res) <- runCommands sm' cmds-    prettyCommands sm' hist-      $ checkCommandNames cmds-      $ res === Ok+  alive <- liftIO $ newTVarIO []+  reg <- liftIO $ unsafeNewRegistry+  let sm' = sm alive reg+  (hist, _model, res) <- runCommands sm' cmds+  prettyCommands sm' hist $+    checkCommandNames cmds $+      res === Ok  unused :: a unused = error "not used during command generation"@@ -606,24 +635,24 @@  _forkCount :: QSM.Commands (At IO Cmd) (At IO Resp) _forkCount = example (sm unused unused) $ do-    run' $ At $ Fork-    run' $ At $ LiveThreads+  run' $ At $ Fork+  run' $ At $ LiveThreads  _forkKillCount :: QSM.Commands (At IO Cmd) (At IO Resp) _forkKillCount = example (sm unused unused) $ do-    [t] <- run $ At $ Fork-    run' $ At $ Terminate t-    run' $ At $ LiveThreads+  [t] <- run $ At $ Fork+  run' $ At $ Terminate t+  run' $ At $ LiveThreads  _forkFromKillCount :: QSM.Commands (At IO Cmd) (At IO Resp) _forkFromKillCount = example (sm unused unused) $ do-    [t] <- run $ At $ Fork-    run' $ At $ ForkFrom t DontLink-    run' $ At $ Terminate t-    run' $ At $ LiveThreads+  [t] <- run $ At $ Fork+  run' $ At $ ForkFrom t DontLink+  run' $ At $ Terminate t+  run' $ At $ LiveThreads  _invalidForkFrom :: QSM.Commands (At IO Cmd) (At IO Resp) _invalidForkFrom = example (sm unused unused) $ do-    [t] <- run $ At $ Fork-    run' $ At $ Terminate t-    run' $ At $ ForkFrom t DontLink+  [t] <- run $ At $ Fork+  run' $ At $ Terminate t+  run' $ At $ ForkFrom t DontLink
test/Test/Util/QSM.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE ScopedTypeVariables #-} -module Test.Util.QSM (-    Example-    -- opaque+module Test.Util.QSM+  ( Example+  -- opaque   , example   , run   , run'@@ -16,8 +16,8 @@ import Test.StateMachine.Types import Test.StateMachine.Types.Rank2 qualified as Rank2 -data Example cmd a =-    Done a+data Example cmd a+  = Done a   | Run (cmd Symbolic) ([Var] -> Example cmd a)   | Fail String @@ -25,13 +25,13 @@   fmap = liftM  instance Applicative (Example cmd) where-  pure  = Done+  pure = Done   (<*>) = ap  instance Monad (Example cmd) where-  return         = pure-  Done a   >>= f = f a-  Run c k  >>= f = Run c (k >=> f)+  return = pure+  Done a >>= f = f a+  Run c k >>= f = Run c (k >=> f)   Fail err >>= _ = Fail err  instance Fail.MonadFail (Example cmd) where@@ -45,30 +45,32 @@ run' :: cmd Symbolic -> Example cmd () run' cmd = Run cmd (\_vars -> Done ()) -example :: forall model cmd m resp. (Rank2.Foldable resp, Show (cmd Symbolic))-        => StateMachine model cmd m resp-        -> Example cmd ()-        -> Commands cmd resp+example ::+  forall model cmd m resp.+  (Rank2.Foldable resp, Show (cmd Symbolic)) =>+  StateMachine model cmd m resp ->+  Example cmd () ->+  Commands cmd resp example sm =-    Commands . fst . flip runGenSym newCounter . go (initModel sm)-  where-    go :: model Symbolic -> Example cmd () -> GenSym [Command cmd resp]-    go _ (Done ())   = return []-    go _ (Fail err)  = error $ "example: " ++ err-    go m (Run cmd k) = do-        case Logic.logic (precondition sm m cmd) of-          Logic.VFalse counterexample ->-            error $ "Invalid command " ++ show cmd ++ ": " ++ show counterexample-          Logic.VTrue -> do-            resp <- mock sm m cmd+  Commands . fst . flip runGenSym newCounter . go (initModel sm)+ where+  go :: model Symbolic -> Example cmd () -> GenSym [Command cmd resp]+  go _ (Done ()) = return []+  go _ (Fail err) = error $ "example: " ++ err+  go m (Run cmd k) = do+    case Logic.logic (precondition sm m cmd) of+      Logic.VFalse counterexample ->+        error $ "Invalid command " ++ show cmd ++ ": " ++ show counterexample+      Logic.VTrue -> do+        resp <- mock sm m cmd -            let m' :: model Symbolic-                m' = transition sm m cmd resp+        let m' :: model Symbolic+            m' = transition sm m cmd resp -                vars :: [Var]-                vars = getUsedVars resp+            vars :: [Var]+            vars = getUsedVars resp -                cmd' :: Command cmd resp-                cmd' = Command cmd resp vars+            cmd' :: Command cmd resp+            cmd' = Command cmd resp vars -            (cmd' :) <$> go m' (k vars)+        (cmd' :) <$> go m' (k vars)
test/Test/Util/SOP.hs view
@@ -1,31 +1,32 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -module Test.Util.SOP (-    constrName+module Test.Util.SOP+  ( constrName   , constrNames   ) where  import Data.Proxy import Generics.SOP qualified as SOP -constrInfo :: SOP.HasDatatypeInfo a-           => proxy a-           -> SOP.NP SOP.ConstructorInfo (SOP.Code a)+constrInfo ::+  SOP.HasDatatypeInfo a =>+  proxy a ->+  SOP.NP SOP.ConstructorInfo (SOP.Code a) constrInfo = SOP.constructorInfo . SOP.datatypeInfo  constrName :: forall a. SOP.HasDatatypeInfo a => a -> String constrName a =-    SOP.hcollapse $ SOP.hliftA2 go (constrInfo p) (SOP.unSOP (SOP.from a))-  where-    go :: SOP.ConstructorInfo b -> SOP.NP SOP.I b -> SOP.K String b-    go nfo _ = SOP.K $ SOP.constructorName nfo+  SOP.hcollapse $ SOP.hliftA2 go (constrInfo p) (SOP.unSOP (SOP.from a))+ where+  go :: SOP.ConstructorInfo b -> SOP.NP SOP.I b -> SOP.K String b+  go nfo _ = SOP.K $ SOP.constructorName nfo -    p = Proxy @a+  p = Proxy @a  constrNames :: SOP.HasDatatypeInfo a => proxy a -> [String] constrNames p =-    SOP.hcollapse $ SOP.hmap go (constrInfo p)-  where-    go :: SOP.ConstructorInfo a -> SOP.K String a-    go nfo = SOP.K $ SOP.constructorName nfo+  SOP.hcollapse $ SOP.hmap go (constrInfo p)+ where+  go :: SOP.ConstructorInfo a -> SOP.K String a+  go nfo = SOP.K $ SOP.constructorName nfo
test/Test/Util/ToExpr.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-}- {-# OPTIONS_GHC -Wno-orphans #-}  -- | This module implements QSM's @CanDiff@ typeclass using @tree-diff@'s@@ -14,14 +13,14 @@ import Test.StateMachine.Types.References qualified as QSM  instance ToExpr x => CanDiff x where-  type ADiff  x = Edit EditExpr+  type ADiff x = Edit EditExpr   type AnExpr x = Expr -  toDiff             = toExpr-  exprDiff         _ = T.exprDiff+  toDiff = toExpr+  exprDiff _ = T.exprDiff   diffToDocCompact _ = ansiWlBgEditExprCompact-  diffToDoc        _ = ansiWlBgEditExpr-  exprToDoc        _ = ansiWlBgExpr+  diffToDoc _ = ansiWlBgEditExpr+  exprToDoc _ = ansiWlBgExpr  {-------------------------------------------------------------------------------   QSM's References instances