{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE UndecidableInstances #-} module Ouroboros.Consensus.Util.ResourceRegistry ( RegistryClosedException (..) , ResourceRegistryThreadException -- * Creating and releasing the registry itself , bracketWithPrivateRegistry , registryThread , withRegistry -- * Allocating and releasing regular resources , ResourceKey , allocate , allocateEither , release , releaseAll , unsafeRelease , unsafeReleaseAll -- * Threads , cancelThread , forkLinkedThread , forkThread , linkToRegistry , threadId , waitAnyThread , waitThread , withThread -- ** opaque , Thread -- * Temporary registry , TempRegistryException (..) , allocateTemp , modifyWithTempRegistry , runInnerWithTempRegistry , runWithTempRegistry -- ** opaque , WithTempRegistry -- * Combinators primarily for testing , closeRegistry , countResources , unsafeNewRegistry -- * opaque , ResourceRegistry ) where import Control.Applicative ((<|>)) import Control.Exception (asyncExceptionFromException) import Control.Monad import Control.Monad.Reader import Control.Monad.State.Strict import Data.Bifunctor import Data.Bimap (Bimap) import qualified Data.Bimap as Bimap import Data.Either (partitionEithers) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (catMaybes, listToMaybe) import Data.Set (Set) import qualified Data.Set as Set import Data.Word (Word64) import GHC.Generics (Generic) import NoThunks.Class (InspectHeapNamed (..), OnlyCheckWhnfNamed (..), allNoThunks) import Ouroboros.Consensus.Util (mustBeRight, whenJust) import Ouroboros.Consensus.Util.CallStack import Ouroboros.Consensus.Util.IOLike import Ouroboros.Consensus.Util.Orphans () -- | Resource registry -- -- 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 'fork' is no -- solution as it means we might leak resources. There is however another -- problem with 'fork'. Consider this snippet: -- -- > withRegistry $ \registry -> -- > r <- allocate registry allocateResource releaseResource -- > fork $ .. 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. data ResourceRegistry m = ResourceRegistry { -- | Context in which the registry was created ResourceRegistry m -> Context m registryContext :: !(Context m) -- | Registry state , ResourceRegistry m -> StrictTVar m (RegistryState m) registryState :: !(StrictTVar m (RegistryState m)) } deriving ((forall x. ResourceRegistry m -> Rep (ResourceRegistry m) x) -> (forall x. Rep (ResourceRegistry m) x -> ResourceRegistry m) -> Generic (ResourceRegistry m) forall x. Rep (ResourceRegistry m) x -> ResourceRegistry m forall x. ResourceRegistry m -> Rep (ResourceRegistry m) x forall a. (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a forall (m :: * -> *) x. Rep (ResourceRegistry m) x -> ResourceRegistry m forall (m :: * -> *) x. ResourceRegistry m -> Rep (ResourceRegistry m) x $cto :: forall (m :: * -> *) x. Rep (ResourceRegistry m) x -> ResourceRegistry m $cfrom :: forall (m :: * -> *) x. ResourceRegistry m -> Rep (ResourceRegistry m) x Generic) deriving instance IOLike m => 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 (Int -> Age -> ShowS [Age] -> ShowS Age -> String (Int -> Age -> ShowS) -> (Age -> String) -> ([Age] -> ShowS) -> Show Age forall a. (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a showList :: [Age] -> ShowS $cshowList :: [Age] -> ShowS show :: Age -> String $cshow :: Age -> String showsPrec :: Int -> Age -> ShowS $cshowsPrec :: Int -> Age -> ShowS Show) deriving newtype (Age -> Age -> Bool (Age -> Age -> Bool) -> (Age -> Age -> Bool) -> Eq Age forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a /= :: Age -> Age -> Bool $c/= :: Age -> Age -> Bool == :: Age -> Age -> Bool $c== :: Age -> Age -> Bool Eq, Eq Age Eq Age -> (Age -> Age -> Ordering) -> (Age -> Age -> Bool) -> (Age -> Age -> Bool) -> (Age -> Age -> Bool) -> (Age -> Age -> Bool) -> (Age -> Age -> Age) -> (Age -> Age -> Age) -> Ord Age Age -> Age -> Bool Age -> Age -> Ordering Age -> Age -> Age forall a. Eq a -> (a -> a -> Ordering) -> (a -> a -> Bool) -> (a -> a -> Bool) -> (a -> a -> Bool) -> (a -> a -> Bool) -> (a -> a -> a) -> (a -> a -> a) -> Ord a min :: Age -> Age -> Age $cmin :: Age -> Age -> Age max :: Age -> Age -> Age $cmax :: Age -> Age -> Age >= :: Age -> Age -> Bool $c>= :: Age -> Age -> Bool > :: Age -> Age -> Bool $c> :: Age -> Age -> Bool <= :: Age -> Age -> Bool $c<= :: Age -> Age -> Bool < :: Age -> Age -> Bool $c< :: Age -> Age -> Bool compare :: Age -> Age -> Ordering $ccompare :: Age -> Age -> Ordering $cp1Ord :: Eq Age Ord) deriving Context -> Age -> IO (Maybe ThunkInfo) Proxy Age -> String (Context -> Age -> IO (Maybe ThunkInfo)) -> (Context -> Age -> IO (Maybe ThunkInfo)) -> (Proxy Age -> String) -> NoThunks Age forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a showTypeOf :: Proxy Age -> String $cshowTypeOf :: Proxy Age -> String wNoThunks :: Context -> Age -> IO (Maybe ThunkInfo) $cwNoThunks :: Context -> Age -> IO (Maybe ThunkInfo) noThunks :: Context -> Age -> IO (Maybe ThunkInfo) $cnoThunks :: Context -> Age -> IO (Maybe ThunkInfo) NoThunks via InspectHeapNamed "Age" Age -- | The age of the first resource successfully allocated in a fresh registry ageOfFirstResource :: Age ageOfFirstResource :: Age ageOfFirstResource = Word64 -> Age Age Word64 forall a. Bounded a => a 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 -> Age nextYoungerAge (Age Word64 n) = Word64 -> Age Age (Word64 n Word64 -> Word64 -> Word64 forall a. Num a => a -> a -> a - Word64 1) -- | Internal registry state -- -- INVARIANT: We record exactly the ages of currently allocated resources, -- @'Bimap.keys' . 'registryAges' = 'Map.keys' . 'registryResources'@. data RegistryState m = RegistryState { -- | Forked threads RegistryState m -> KnownThreads m registryThreads :: !(KnownThreads m) -- | Currently allocated resources , RegistryState m -> Map ResourceId (Resource m) registryResources :: !(Map ResourceId (Resource m)) -- | Next available resource key , RegistryState m -> ResourceId 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. , RegistryState m -> Bimap ResourceId Age registryAges :: !(Bimap ResourceId Age) -- | The age of the next resource , RegistryState m -> Age registryNextAge :: !Age -- | Does the registry still accept new allocations? -- -- See 'RegistryClosedException' for discussion. , RegistryState m -> RegistryStatus registryStatus :: !RegistryStatus } deriving ((forall x. RegistryState m -> Rep (RegistryState m) x) -> (forall x. Rep (RegistryState m) x -> RegistryState m) -> Generic (RegistryState m) forall x. Rep (RegistryState m) x -> RegistryState m forall x. RegistryState m -> Rep (RegistryState m) x forall a. (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a forall (m :: * -> *) x. Rep (RegistryState m) x -> RegistryState m forall (m :: * -> *) x. RegistryState m -> Rep (RegistryState m) x $cto :: forall (m :: * -> *) x. Rep (RegistryState m) x -> RegistryState m $cfrom :: forall (m :: * -> *) x. RegistryState m -> Rep (RegistryState m) x Generic, Context -> RegistryState m -> IO (Maybe ThunkInfo) Proxy (RegistryState m) -> String (Context -> RegistryState m -> IO (Maybe ThunkInfo)) -> (Context -> RegistryState m -> IO (Maybe ThunkInfo)) -> (Proxy (RegistryState m) -> String) -> NoThunks (RegistryState m) forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a forall (m :: * -> *). Context -> RegistryState m -> IO (Maybe ThunkInfo) forall (m :: * -> *). Proxy (RegistryState m) -> String showTypeOf :: Proxy (RegistryState m) -> String $cshowTypeOf :: forall (m :: * -> *). Proxy (RegistryState m) -> String wNoThunks :: Context -> RegistryState m -> IO (Maybe ThunkInfo) $cwNoThunks :: forall (m :: * -> *). Context -> RegistryState m -> IO (Maybe ThunkInfo) noThunks :: Context -> RegistryState m -> IO (Maybe ThunkInfo) $cnoThunks :: forall (m :: * -> *). Context -> RegistryState m -> IO (Maybe ThunkInfo) NoThunks) -- | The currently allocated keys in youngest-to-oldest order getYoungestToOldest :: RegistryState m -> [ResourceId] getYoungestToOldest :: RegistryState m -> [ResourceId] getYoungestToOldest = ((Age, ResourceId) -> ResourceId) -> [(Age, ResourceId)] -> [ResourceId] forall a b. (a -> b) -> [a] -> [b] map (Age, ResourceId) -> ResourceId forall a b. (a, b) -> b snd ([(Age, ResourceId)] -> [ResourceId]) -> (RegistryState m -> [(Age, ResourceId)]) -> RegistryState m -> [ResourceId] forall b c a. (b -> c) -> (a -> b) -> a -> c . Bimap ResourceId Age -> [(Age, ResourceId)] forall a b. Bimap a b -> [(b, a)] Bimap.toAscListR (Bimap ResourceId Age -> [(Age, ResourceId)]) -> (RegistryState m -> Bimap ResourceId Age) -> RegistryState m -> [(Age, ResourceId)] forall b c a. (b -> c) -> (a -> b) -> a -> c . RegistryState m -> Bimap ResourceId Age forall (m :: * -> *). RegistryState m -> Bimap ResourceId Age 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 Context -> KnownThreads m -> IO (Maybe ThunkInfo) Proxy (KnownThreads m) -> String (Context -> KnownThreads m -> IO (Maybe ThunkInfo)) -> (Context -> KnownThreads m -> IO (Maybe ThunkInfo)) -> (Proxy (KnownThreads m) -> String) -> NoThunks (KnownThreads m) forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a forall (m :: * -> *). Context -> KnownThreads m -> IO (Maybe ThunkInfo) forall (m :: * -> *). Proxy (KnownThreads m) -> String showTypeOf :: Proxy (KnownThreads m) -> String $cshowTypeOf :: forall (m :: * -> *). Proxy (KnownThreads m) -> String wNoThunks :: Context -> KnownThreads m -> IO (Maybe ThunkInfo) $cwNoThunks :: forall (m :: * -> *). Context -> KnownThreads m -> IO (Maybe ThunkInfo) noThunks :: Context -> KnownThreads m -> IO (Maybe ThunkInfo) $cnoThunks :: forall (m :: * -> *). Context -> KnownThreads m -> IO (Maybe ThunkInfo) 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 ((forall x. RegistryStatus -> Rep RegistryStatus x) -> (forall x. Rep RegistryStatus x -> RegistryStatus) -> Generic RegistryStatus forall x. Rep RegistryStatus x -> RegistryStatus forall x. RegistryStatus -> Rep RegistryStatus x forall a. (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a $cto :: forall x. Rep RegistryStatus x -> RegistryStatus $cfrom :: forall x. RegistryStatus -> Rep RegistryStatus x Generic, Context -> RegistryStatus -> IO (Maybe ThunkInfo) Proxy RegistryStatus -> String (Context -> RegistryStatus -> IO (Maybe ThunkInfo)) -> (Context -> RegistryStatus -> IO (Maybe ThunkInfo)) -> (Proxy RegistryStatus -> String) -> NoThunks RegistryStatus forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a showTypeOf :: Proxy RegistryStatus -> String $cshowTypeOf :: Proxy RegistryStatus -> String wNoThunks :: Context -> RegistryStatus -> IO (Maybe ThunkInfo) $cwNoThunks :: Context -> RegistryStatus -> IO (Maybe ThunkInfo) noThunks :: Context -> RegistryStatus -> IO (Maybe ThunkInfo) $cnoThunks :: Context -> RegistryStatus -> IO (Maybe ThunkInfo) NoThunks) -- | Resource key -- -- Resource keys are tied to a particular registry. data ResourceKey m = ResourceKey !(ResourceRegistry m) !ResourceId deriving ((forall x. ResourceKey m -> Rep (ResourceKey m) x) -> (forall x. Rep (ResourceKey m) x -> ResourceKey m) -> Generic (ResourceKey m) forall x. Rep (ResourceKey m) x -> ResourceKey m forall x. ResourceKey m -> Rep (ResourceKey m) x forall a. (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a forall (m :: * -> *) x. Rep (ResourceKey m) x -> ResourceKey m forall (m :: * -> *) x. ResourceKey m -> Rep (ResourceKey m) x $cto :: forall (m :: * -> *) x. Rep (ResourceKey m) x -> ResourceKey m $cfrom :: forall (m :: * -> *) x. ResourceKey m -> Rep (ResourceKey m) x Generic, Context -> ResourceKey m -> IO (Maybe ThunkInfo) Proxy (ResourceKey m) -> String (Context -> ResourceKey m -> IO (Maybe ThunkInfo)) -> (Context -> ResourceKey m -> IO (Maybe ThunkInfo)) -> (Proxy (ResourceKey m) -> String) -> NoThunks (ResourceKey m) forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a forall (m :: * -> *). IOLike m => Context -> ResourceKey m -> IO (Maybe ThunkInfo) forall (m :: * -> *). IOLike m => Proxy (ResourceKey m) -> String showTypeOf :: Proxy (ResourceKey m) -> String $cshowTypeOf :: forall (m :: * -> *). IOLike m => Proxy (ResourceKey m) -> String wNoThunks :: Context -> ResourceKey m -> IO (Maybe ThunkInfo) $cwNoThunks :: forall (m :: * -> *). IOLike m => Context -> ResourceKey m -> IO (Maybe ThunkInfo) noThunks :: Context -> ResourceKey m -> IO (Maybe ThunkInfo) $cnoThunks :: forall (m :: * -> *). IOLike m => Context -> ResourceKey m -> IO (Maybe ThunkInfo) NoThunks) -- | Return the 'ResourceId' of a 'ResourceKey'. resourceKeyId :: ResourceKey m -> ResourceId resourceKeyId :: ResourceKey m -> ResourceId resourceKeyId (ResourceKey ResourceRegistry m _rr ResourceId rid) = ResourceId rid -- | Resource ID -- -- This uniquifying data is not exposed by the 'ResourceRegistry' interface. newtype ResourceId = ResourceId Int deriving stock (Int -> ResourceId -> ShowS [ResourceId] -> ShowS ResourceId -> String (Int -> ResourceId -> ShowS) -> (ResourceId -> String) -> ([ResourceId] -> ShowS) -> Show ResourceId forall a. (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a showList :: [ResourceId] -> ShowS $cshowList :: [ResourceId] -> ShowS show :: ResourceId -> String $cshow :: ResourceId -> String showsPrec :: Int -> ResourceId -> ShowS $cshowsPrec :: Int -> ResourceId -> ShowS Show, ResourceId -> ResourceId -> Bool (ResourceId -> ResourceId -> Bool) -> (ResourceId -> ResourceId -> Bool) -> Eq ResourceId forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a /= :: ResourceId -> ResourceId -> Bool $c/= :: ResourceId -> ResourceId -> Bool == :: ResourceId -> ResourceId -> Bool $c== :: ResourceId -> ResourceId -> Bool Eq, Eq ResourceId Eq ResourceId -> (ResourceId -> ResourceId -> Ordering) -> (ResourceId -> ResourceId -> Bool) -> (ResourceId -> ResourceId -> Bool) -> (ResourceId -> ResourceId -> Bool) -> (ResourceId -> ResourceId -> Bool) -> (ResourceId -> ResourceId -> ResourceId) -> (ResourceId -> ResourceId -> ResourceId) -> Ord ResourceId ResourceId -> ResourceId -> Bool ResourceId -> ResourceId -> Ordering ResourceId -> ResourceId -> ResourceId forall a. Eq a -> (a -> a -> Ordering) -> (a -> a -> Bool) -> (a -> a -> Bool) -> (a -> a -> Bool) -> (a -> a -> Bool) -> (a -> a -> a) -> (a -> a -> a) -> Ord a min :: ResourceId -> ResourceId -> ResourceId $cmin :: ResourceId -> ResourceId -> ResourceId max :: ResourceId -> ResourceId -> ResourceId $cmax :: ResourceId -> ResourceId -> ResourceId >= :: ResourceId -> ResourceId -> Bool $c>= :: ResourceId -> ResourceId -> Bool > :: ResourceId -> ResourceId -> Bool $c> :: ResourceId -> ResourceId -> Bool <= :: ResourceId -> ResourceId -> Bool $c<= :: ResourceId -> ResourceId -> Bool < :: ResourceId -> ResourceId -> Bool $c< :: ResourceId -> ResourceId -> Bool compare :: ResourceId -> ResourceId -> Ordering $ccompare :: ResourceId -> ResourceId -> Ordering $cp1Ord :: Eq ResourceId Ord) deriving newtype (Int -> ResourceId ResourceId -> Int ResourceId -> [ResourceId] ResourceId -> ResourceId ResourceId -> ResourceId -> [ResourceId] ResourceId -> ResourceId -> ResourceId -> [ResourceId] (ResourceId -> ResourceId) -> (ResourceId -> ResourceId) -> (Int -> ResourceId) -> (ResourceId -> Int) -> (ResourceId -> [ResourceId]) -> (ResourceId -> ResourceId -> [ResourceId]) -> (ResourceId -> ResourceId -> [ResourceId]) -> (ResourceId -> ResourceId -> ResourceId -> [ResourceId]) -> Enum ResourceId forall a. (a -> a) -> (a -> a) -> (Int -> a) -> (a -> Int) -> (a -> [a]) -> (a -> a -> [a]) -> (a -> a -> [a]) -> (a -> a -> a -> [a]) -> Enum a enumFromThenTo :: ResourceId -> ResourceId -> ResourceId -> [ResourceId] $cenumFromThenTo :: ResourceId -> ResourceId -> ResourceId -> [ResourceId] enumFromTo :: ResourceId -> ResourceId -> [ResourceId] $cenumFromTo :: ResourceId -> ResourceId -> [ResourceId] enumFromThen :: ResourceId -> ResourceId -> [ResourceId] $cenumFromThen :: ResourceId -> ResourceId -> [ResourceId] enumFrom :: ResourceId -> [ResourceId] $cenumFrom :: ResourceId -> [ResourceId] fromEnum :: ResourceId -> Int $cfromEnum :: ResourceId -> Int toEnum :: Int -> ResourceId $ctoEnum :: Int -> ResourceId pred :: ResourceId -> ResourceId $cpred :: ResourceId -> ResourceId succ :: ResourceId -> ResourceId $csucc :: ResourceId -> ResourceId Enum, Context -> ResourceId -> IO (Maybe ThunkInfo) Proxy ResourceId -> String (Context -> ResourceId -> IO (Maybe ThunkInfo)) -> (Context -> ResourceId -> IO (Maybe ThunkInfo)) -> (Proxy ResourceId -> String) -> NoThunks ResourceId forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a showTypeOf :: Proxy ResourceId -> String $cshowTypeOf :: Proxy ResourceId -> String wNoThunks :: Context -> ResourceId -> IO (Maybe ThunkInfo) $cwNoThunks :: Context -> ResourceId -> IO (Maybe ThunkInfo) noThunks :: Context -> ResourceId -> IO (Maybe ThunkInfo) $cnoThunks :: Context -> ResourceId -> IO (Maybe ThunkInfo) NoThunks) -- | Information about a resource data Resource m = Resource { -- | Context in which the resource was created Resource m -> Context m resourceContext :: !(Context m) -- | Deallocate the resource , Resource m -> Release m resourceRelease :: !(Release m) } deriving ((forall x. Resource m -> Rep (Resource m) x) -> (forall x. Rep (Resource m) x -> Resource m) -> Generic (Resource m) forall x. Rep (Resource m) x -> Resource m forall x. Resource m -> Rep (Resource m) x forall a. (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a forall (m :: * -> *) x. Rep (Resource m) x -> Resource m forall (m :: * -> *) x. Resource m -> Rep (Resource m) x $cto :: forall (m :: * -> *) x. Rep (Resource m) x -> Resource m $cfrom :: forall (m :: * -> *) x. Resource m -> Rep (Resource m) x Generic, Context -> Resource m -> IO (Maybe ThunkInfo) Proxy (Resource m) -> String (Context -> Resource m -> IO (Maybe ThunkInfo)) -> (Context -> Resource m -> IO (Maybe ThunkInfo)) -> (Proxy (Resource m) -> String) -> NoThunks (Resource m) forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a forall (m :: * -> *). Context -> Resource m -> IO (Maybe ThunkInfo) forall (m :: * -> *). Proxy (Resource m) -> String showTypeOf :: Proxy (Resource m) -> String $cshowTypeOf :: forall (m :: * -> *). Proxy (Resource m) -> String wNoThunks :: Context -> Resource m -> IO (Maybe ThunkInfo) $cwNoThunks :: forall (m :: * -> *). Context -> Resource m -> IO (Maybe ThunkInfo) noThunks :: Context -> Resource m -> IO (Maybe ThunkInfo) $cnoThunks :: forall (m :: * -> *). Context -> Resource m -> IO (Maybe ThunkInfo) 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 Context -> Release m -> IO (Maybe ThunkInfo) Proxy (Release m) -> String (Context -> Release m -> IO (Maybe ThunkInfo)) -> (Context -> Release m -> IO (Maybe ThunkInfo)) -> (Proxy (Release m) -> String) -> NoThunks (Release m) forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a forall (m :: * -> *). Context -> Release m -> IO (Maybe ThunkInfo) forall (m :: * -> *). Proxy (Release m) -> String showTypeOf :: Proxy (Release m) -> String $cshowTypeOf :: forall (m :: * -> *). Proxy (Release m) -> String wNoThunks :: Context -> Release m -> IO (Maybe ThunkInfo) $cwNoThunks :: forall (m :: * -> *). Context -> Release m -> IO (Maybe ThunkInfo) noThunks :: Context -> Release m -> IO (Maybe ThunkInfo) $cnoThunks :: forall (m :: * -> *). Context -> Release m -> IO (Maybe ThunkInfo) NoThunks via OnlyCheckWhnfNamed "Release" (Release m) releaseResource :: Resource m -> m Bool releaseResource :: Resource m -> m Bool releaseResource Resource{resourceRelease :: forall (m :: * -> *). Resource m -> Release m resourceRelease = Release m Bool f} = m Bool f instance Show (Release m) where show :: Release m -> String show Release m _ = String "<<release>>" {------------------------------------------------------------------------------- Internal: pure functions on the registry state -------------------------------------------------------------------------------} modifyKnownThreads :: (Set (ThreadId m) -> Set (ThreadId m)) -> KnownThreads m -> KnownThreads m modifyKnownThreads :: (Set (ThreadId m) -> Set (ThreadId m)) -> KnownThreads m -> KnownThreads m modifyKnownThreads Set (ThreadId m) -> Set (ThreadId m) f (KnownThreads Set (ThreadId m) ts) = Set (ThreadId m) -> KnownThreads m forall (m :: * -> *). Set (ThreadId m) -> KnownThreads m KnownThreads (Set (ThreadId m) -> Set (ThreadId m) f Set (ThreadId m) ts) -- | Auxiliary for functions that should be disallowed when registry is closed unlessClosed :: State (RegistryState m) a -> State (RegistryState m) (Either PrettyCallStack a) unlessClosed :: State (RegistryState m) a -> State (RegistryState m) (Either PrettyCallStack a) unlessClosed State (RegistryState m) a f = do RegistryStatus status <- (RegistryState m -> RegistryStatus) -> StateT (RegistryState m) Identity RegistryStatus forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a gets RegistryState m -> RegistryStatus forall (m :: * -> *). RegistryState m -> RegistryStatus registryStatus case RegistryStatus status of RegistryClosed PrettyCallStack closed -> Either PrettyCallStack a -> State (RegistryState m) (Either PrettyCallStack a) forall (m :: * -> *) a. Monad m => a -> m a return (Either PrettyCallStack a -> State (RegistryState m) (Either PrettyCallStack a)) -> Either PrettyCallStack a -> State (RegistryState m) (Either PrettyCallStack a) forall a b. (a -> b) -> a -> b $ PrettyCallStack -> Either PrettyCallStack a forall a b. a -> Either a b Left PrettyCallStack closed RegistryStatus RegistryOpen -> a -> Either PrettyCallStack a forall a b. b -> Either a b Right (a -> Either PrettyCallStack a) -> State (RegistryState m) a -> State (RegistryState m) (Either PrettyCallStack a) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> State (RegistryState m) a f -- | Allocate key for new resource allocKey :: State (RegistryState m) (Either PrettyCallStack ResourceId) allocKey :: State (RegistryState m) (Either PrettyCallStack ResourceId) allocKey = State (RegistryState m) ResourceId -> State (RegistryState m) (Either PrettyCallStack ResourceId) forall (m :: * -> *) a. State (RegistryState m) a -> State (RegistryState m) (Either PrettyCallStack a) unlessClosed (State (RegistryState m) ResourceId -> State (RegistryState m) (Either PrettyCallStack ResourceId)) -> State (RegistryState m) ResourceId -> State (RegistryState m) (Either PrettyCallStack ResourceId) forall a b. (a -> b) -> a -> b $ do ResourceId nextKey <- (RegistryState m -> ResourceId) -> State (RegistryState m) ResourceId forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a gets RegistryState m -> ResourceId forall (m :: * -> *). RegistryState m -> ResourceId registryNextKey (RegistryState m -> RegistryState m) -> StateT (RegistryState m) Identity () forall s (m :: * -> *). MonadState s m => (s -> s) -> m () modify ((RegistryState m -> RegistryState m) -> StateT (RegistryState m) Identity ()) -> (RegistryState m -> RegistryState m) -> StateT (RegistryState m) Identity () forall a b. (a -> b) -> a -> b $ \RegistryState m st -> RegistryState m st {registryNextKey :: ResourceId registryNextKey = ResourceId -> ResourceId forall a. Enum a => a -> a succ ResourceId nextKey} ResourceId -> State (RegistryState m) ResourceId forall (m :: * -> *) a. Monad m => a -> m a return ResourceId nextKey -- | Insert new resource insertResource :: ResourceId -> Resource m -> State (RegistryState m) (Either PrettyCallStack ()) insertResource :: ResourceId -> Resource m -> State (RegistryState m) (Either PrettyCallStack ()) insertResource ResourceId key Resource m r = State (RegistryState m) () -> State (RegistryState m) (Either PrettyCallStack ()) forall (m :: * -> *) a. State (RegistryState m) a -> State (RegistryState m) (Either PrettyCallStack a) unlessClosed (State (RegistryState m) () -> State (RegistryState m) (Either PrettyCallStack ())) -> State (RegistryState m) () -> State (RegistryState m) (Either PrettyCallStack ()) forall a b. (a -> b) -> a -> b $ do (RegistryState m -> RegistryState m) -> State (RegistryState m) () forall s (m :: * -> *). MonadState s m => (s -> s) -> m () modify ((RegistryState m -> RegistryState m) -> State (RegistryState m) ()) -> (RegistryState m -> RegistryState m) -> State (RegistryState m) () forall a b. (a -> b) -> a -> b $ \RegistryState m st -> RegistryState m st { registryResources :: Map ResourceId (Resource m) registryResources = ResourceId -> Resource m -> Map ResourceId (Resource m) -> Map ResourceId (Resource m) forall k a. Ord k => k -> a -> Map k a -> Map k a Map.insert ResourceId key Resource m r (RegistryState m -> Map ResourceId (Resource m) forall (m :: * -> *). RegistryState m -> Map ResourceId (Resource m) registryResources RegistryState m st) , registryAges :: Bimap ResourceId Age registryAges = ResourceId -> Age -> Bimap ResourceId Age -> Bimap ResourceId Age forall a b. (Ord a, Ord b) => a -> b -> Bimap a b -> Bimap a b Bimap.insert ResourceId key (RegistryState m -> Age forall (m :: * -> *). RegistryState m -> Age registryNextAge RegistryState m st) (RegistryState m -> Bimap ResourceId Age forall (m :: * -> *). RegistryState m -> Bimap ResourceId Age registryAges RegistryState m st) , registryNextAge :: Age registryNextAge = Age -> Age nextYoungerAge (RegistryState m -> Age forall (m :: * -> *). RegistryState m -> Age registryNextAge RegistryState m st) } -- | Remove resource from the registry (if it exists) removeResource :: ResourceId -> State (RegistryState m) (Maybe (Resource m)) removeResource :: ResourceId -> State (RegistryState m) (Maybe (Resource m)) removeResource ResourceId key = (RegistryState m -> (Maybe (Resource m), RegistryState m)) -> State (RegistryState m) (Maybe (Resource m)) forall s (m :: * -> *) a. MonadState s m => (s -> (a, s)) -> m a state ((RegistryState m -> (Maybe (Resource m), RegistryState m)) -> State (RegistryState m) (Maybe (Resource m))) -> (RegistryState m -> (Maybe (Resource m), RegistryState m)) -> State (RegistryState m) (Maybe (Resource m)) forall a b. (a -> b) -> a -> b $ \RegistryState m st -> let (Maybe (Resource m) mbResource, Map ResourceId (Resource m) resources') = (ResourceId -> Resource m -> Maybe (Resource m)) -> ResourceId -> Map ResourceId (Resource m) -> (Maybe (Resource m), Map ResourceId (Resource m)) forall k a. Ord k => (k -> a -> Maybe a) -> k -> Map k a -> (Maybe a, Map k a) Map.updateLookupWithKey (\ResourceId _ Resource m _ -> Maybe (Resource m) forall a. Maybe a Nothing) ResourceId key (RegistryState m -> Map ResourceId (Resource m) forall (m :: * -> *). RegistryState m -> Map ResourceId (Resource m) registryResources RegistryState m st) st' :: RegistryState m st' = RegistryState m st { registryResources :: Map ResourceId (Resource m) registryResources = Map ResourceId (Resource m) resources' , registryAges :: Bimap ResourceId Age registryAges = ResourceId -> Bimap ResourceId Age -> Bimap ResourceId Age forall a b. (Ord a, Ord b) => a -> Bimap a b -> Bimap a b Bimap.delete ResourceId key (RegistryState m -> Bimap ResourceId Age forall (m :: * -> *). RegistryState m -> Bimap ResourceId Age registryAges RegistryState m st) } in (Maybe (Resource m) mbResource, RegistryState m st') -- | Insert thread into the set of known threads insertThread :: IOLike m => ThreadId m -> State (RegistryState m) () insertThread :: ThreadId m -> State (RegistryState m) () insertThread ThreadId m tid = (RegistryState m -> RegistryState m) -> State (RegistryState m) () forall s (m :: * -> *). MonadState s m => (s -> s) -> m () modify ((RegistryState m -> RegistryState m) -> State (RegistryState m) ()) -> (RegistryState m -> RegistryState m) -> State (RegistryState m) () forall a b. (a -> b) -> a -> b $ \RegistryState m st -> RegistryState m st { registryThreads :: KnownThreads m registryThreads = (Set (ThreadId m) -> Set (ThreadId m)) -> KnownThreads m -> KnownThreads m forall (m :: * -> *). (Set (ThreadId m) -> Set (ThreadId m)) -> KnownThreads m -> KnownThreads m modifyKnownThreads (ThreadId m -> Set (ThreadId m) -> Set (ThreadId m) forall a. Ord a => a -> Set a -> Set a Set.insert ThreadId m tid) (KnownThreads m -> KnownThreads m) -> KnownThreads m -> KnownThreads m forall a b. (a -> b) -> a -> b $ RegistryState m -> KnownThreads m forall (m :: * -> *). RegistryState m -> KnownThreads m registryThreads RegistryState m st } -- | Remove thread from set of known threads removeThread :: IOLike m => ThreadId m -> State (RegistryState m) () removeThread :: ThreadId m -> State (RegistryState m) () removeThread ThreadId m tid = (RegistryState m -> RegistryState m) -> State (RegistryState m) () forall s (m :: * -> *). MonadState s m => (s -> s) -> m () modify ((RegistryState m -> RegistryState m) -> State (RegistryState m) ()) -> (RegistryState m -> RegistryState m) -> State (RegistryState m) () forall a b. (a -> b) -> a -> b $ \RegistryState m st -> RegistryState m st { registryThreads :: KnownThreads m registryThreads = (Set (ThreadId m) -> Set (ThreadId m)) -> KnownThreads m -> KnownThreads m forall (m :: * -> *). (Set (ThreadId m) -> Set (ThreadId m)) -> KnownThreads m -> KnownThreads m modifyKnownThreads (ThreadId m -> Set (ThreadId m) -> Set (ThreadId m) forall a. Ord a => a -> Set a -> Set a Set.delete ThreadId m tid) (KnownThreads m -> KnownThreads m) -> KnownThreads m -> KnownThreads m forall a b. (a -> b) -> a -> b $ RegistryState m -> KnownThreads m forall (m :: * -> *). RegistryState m -> KnownThreads m registryThreads RegistryState m 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 :: PrettyCallStack -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) close PrettyCallStack closeCallStack = State (RegistryState m) [ResourceId] -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) forall (m :: * -> *) a. State (RegistryState m) a -> State (RegistryState m) (Either PrettyCallStack a) unlessClosed (State (RegistryState m) [ResourceId] -> State (RegistryState m) (Either PrettyCallStack [ResourceId])) -> State (RegistryState m) [ResourceId] -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) forall a b. (a -> b) -> a -> b $ do (RegistryState m -> RegistryState m) -> StateT (RegistryState m) Identity () forall s (m :: * -> *). MonadState s m => (s -> s) -> m () modify ((RegistryState m -> RegistryState m) -> StateT (RegistryState m) Identity ()) -> (RegistryState m -> RegistryState m) -> StateT (RegistryState m) Identity () forall a b. (a -> b) -> a -> b $ \RegistryState m st -> RegistryState m st {registryStatus :: RegistryStatus registryStatus = PrettyCallStack -> RegistryStatus RegistryClosed PrettyCallStack closeCallStack} (RegistryState m -> [ResourceId]) -> State (RegistryState m) [ResourceId] forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a gets RegistryState m -> [ResourceId] forall (m :: * -> *). RegistryState m -> [ResourceId] getYoungestToOldest -- | Convenience function for updating the registry state updateState :: forall m a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState :: ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr State (RegistryState m) a f = STM m a -> m a forall (m :: * -> *) a. (MonadSTM m, HasCallStack) => STM m a -> m a atomically (STM m a -> m a) -> STM m a -> m a forall a b. (a -> b) -> a -> b $ StrictTVar m (RegistryState m) -> (RegistryState m -> (a, RegistryState m)) -> STM m a forall (m :: * -> *) s a. MonadSTM m => StrictTVar m s -> (s -> (a, s)) -> STM m a stateTVar (ResourceRegistry m -> StrictTVar m (RegistryState m) forall (m :: * -> *). ResourceRegistry m -> StrictTVar m (RegistryState m) registryState ResourceRegistry m rr) (State (RegistryState m) a -> RegistryState m -> (a, RegistryState m) forall s a. State s a -> s -> (a, s) runState State (RegistryState m) a 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 'ThreadKilled' exception. data RegistryClosedException = forall m. IOLike m => RegistryClosedException { -- | The context in which the registry was created () registryClosedRegistryContext :: !(Context m) -- | Callstack to the call to 'close' -- -- Note that 'close' can only be called from the same thread that -- created the registry. , RegistryClosedException -> PrettyCallStack 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 :: (IOLike m, HasCallStack) => m (ResourceRegistry m) unsafeNewRegistry :: m (ResourceRegistry m) unsafeNewRegistry = do Context m context <- m (Context m) forall (m :: * -> *). (IOLike m, HasCallStack) => m (Context m) captureContext StrictTVar m (RegistryState m) stateVar <- RegistryState m -> m (StrictTVar m (RegistryState m)) forall (m :: * -> *) a. (MonadSTM m, HasCallStack, NoThunks a) => a -> m (StrictTVar m a) newTVarIO RegistryState m forall (m :: * -> *). RegistryState m initState ResourceRegistry m -> m (ResourceRegistry m) forall (m :: * -> *) a. Monad m => a -> m a return ResourceRegistry :: forall (m :: * -> *). Context m -> StrictTVar m (RegistryState m) -> ResourceRegistry m ResourceRegistry { registryContext :: Context m registryContext = Context m context , registryState :: StrictTVar m (RegistryState m) registryState = StrictTVar m (RegistryState m) stateVar } where initState :: RegistryState m initState :: RegistryState m initState = RegistryState :: forall (m :: * -> *). KnownThreads m -> Map ResourceId (Resource m) -> ResourceId -> Bimap ResourceId Age -> Age -> RegistryStatus -> RegistryState m RegistryState { registryThreads :: KnownThreads m registryThreads = Set (ThreadId m) -> KnownThreads m forall (m :: * -> *). Set (ThreadId m) -> KnownThreads m KnownThreads Set (ThreadId m) forall a. Set a Set.empty , registryResources :: Map ResourceId (Resource m) registryResources = Map ResourceId (Resource m) forall k a. Map k a Map.empty , registryNextKey :: ResourceId registryNextKey = Int -> ResourceId ResourceId Int 1 , registryAges :: Bimap ResourceId Age registryAges = Bimap ResourceId Age forall a b. Bimap a b Bimap.empty , registryNextAge :: Age registryNextAge = Age ageOfFirstResource , registryStatus :: RegistryStatus registryStatus = 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 :: (IOLike m, HasCallStack) => ResourceRegistry m -> m () closeRegistry :: ResourceRegistry m -> m () closeRegistry ResourceRegistry m rr = m () -> m () forall (m :: * -> *) a. MonadMask m => m a -> m a mask_ (m () -> m ()) -> m () -> m () forall a b. (a -> b) -> a -> b $ do Context m context <- m (Context m) forall (m :: * -> *). (IOLike m, HasCallStack) => m (Context m) captureContext Bool -> m () -> m () forall (f :: * -> *). Applicative f => Bool -> f () -> f () unless (Context m -> ThreadId m forall (m :: * -> *). Context m -> ThreadId m contextThreadId Context m context ThreadId m -> ThreadId m -> Bool forall a. Eq a => a -> a -> Bool == Context m -> ThreadId m forall (m :: * -> *). Context m -> ThreadId m contextThreadId (ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext ResourceRegistry m rr)) (m () -> m ()) -> m () -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistryThreadException -> m () forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a throwIO (ResourceRegistryThreadException -> m ()) -> ResourceRegistryThreadException -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistryClosedFromWrongThread :: forall (m :: * -> *). IOLike m => Context m -> Context m -> ResourceRegistryThreadException ResourceRegistryClosedFromWrongThread { resourceRegistryCreatedIn :: Context m resourceRegistryCreatedIn = ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext ResourceRegistry m rr , resourceRegistryUsedIn :: Context m resourceRegistryUsedIn = Context m context } -- Close the registry so that we cannot allocate any further resources Either PrettyCallStack [ResourceId] alreadyClosed <- ResourceRegistry m -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) -> m (Either PrettyCallStack [ResourceId]) forall (m :: * -> *) a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr (State (RegistryState m) (Either PrettyCallStack [ResourceId]) -> m (Either PrettyCallStack [ResourceId])) -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) -> m (Either PrettyCallStack [ResourceId]) forall a b. (a -> b) -> a -> b $ PrettyCallStack -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) forall (m :: * -> *). PrettyCallStack -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) close (Context m -> PrettyCallStack forall (m :: * -> *). Context m -> PrettyCallStack contextCallStack Context m context) case Either PrettyCallStack [ResourceId] alreadyClosed of Left PrettyCallStack _ -> () -> m () forall (m :: * -> *) a. Monad m => a -> m a return () Right [ResourceId] 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. m [Context m] -> m () forall (f :: * -> *) a. Functor f => f a -> f () void (m [Context m] -> m ()) -> m [Context m] -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistry m -> [ResourceId] -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] forall (m :: * -> *). IOLike m => ResourceRegistry m -> [ResourceId] -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] releaseResources ResourceRegistry m rr [ResourceId] keys ResourceKey m -> m (Maybe (Context m)) forall (m :: * -> *). (IOLike m, HasCallStack) => ResourceKey m -> m (Maybe (Context m)) 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 :: IOLike 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 :: ResourceRegistry m -> [ResourceId] -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] releaseResources ResourceRegistry m rr [ResourceId] sortedKeys ResourceKey m -> m (Maybe (Context m)) releaser = do ([SomeException] exs, [Maybe (Context m)] mbContexts) <- ([Either SomeException (Maybe (Context m))] -> ([SomeException], [Maybe (Context m)])) -> m [Either SomeException (Maybe (Context m))] -> m ([SomeException], [Maybe (Context m)]) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap [Either SomeException (Maybe (Context m))] -> ([SomeException], [Maybe (Context m)]) forall a b. [Either a b] -> ([a], [b]) partitionEithers (m [Either SomeException (Maybe (Context m))] -> m ([SomeException], [Maybe (Context m)])) -> m [Either SomeException (Maybe (Context m))] -> m ([SomeException], [Maybe (Context m)]) forall a b. (a -> b) -> a -> b $ [ResourceId] -> (ResourceId -> m (Either SomeException (Maybe (Context m)))) -> m [Either SomeException (Maybe (Context m))] forall (t :: * -> *) (m :: * -> *) a b. (Traversable t, Monad m) => t a -> (a -> m b) -> m (t b) forM [ResourceId] sortedKeys ((ResourceId -> m (Either SomeException (Maybe (Context m)))) -> m [Either SomeException (Maybe (Context m))]) -> (ResourceId -> m (Either SomeException (Maybe (Context m)))) -> m [Either SomeException (Maybe (Context m))] forall a b. (a -> b) -> a -> b $ m (Maybe (Context m)) -> m (Either SomeException (Maybe (Context m))) forall (m :: * -> *) e a. (MonadCatch m, Exception e) => m a -> m (Either e a) try (m (Maybe (Context m)) -> m (Either SomeException (Maybe (Context m)))) -> (ResourceId -> m (Maybe (Context m))) -> ResourceId -> m (Either SomeException (Maybe (Context m))) forall b c a. (b -> c) -> (a -> b) -> a -> c . ResourceKey m -> m (Maybe (Context m)) releaser (ResourceKey m -> m (Maybe (Context m))) -> (ResourceId -> ResourceKey m) -> ResourceId -> m (Maybe (Context m)) forall b c a. (b -> c) -> (a -> b) -> a -> c . ResourceRegistry m -> ResourceId -> ResourceKey m forall (m :: * -> *). ResourceRegistry m -> ResourceId -> ResourceKey m ResourceKey ResourceRegistry m rr case [SomeException] -> Maybe SomeException prioritize [SomeException] exs of Maybe SomeException Nothing -> [Context m] -> m [Context m] forall (m :: * -> *) a. Monad m => a -> m a return ([Maybe (Context m)] -> [Context m] forall a. [Maybe a] -> [a] catMaybes [Maybe (Context m)] mbContexts) Just SomeException e -> SomeException -> m [Context m] forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a throwIO SomeException e where prioritize :: [SomeException] -> Maybe SomeException prioritize :: [SomeException] -> Maybe SomeException prioritize = (\([SomeException] asyncEx, [SomeException] otherEx) -> [SomeException] -> Maybe SomeException forall a. [a] -> Maybe a listToMaybe [SomeException] asyncEx Maybe SomeException -> Maybe SomeException -> Maybe SomeException forall (f :: * -> *) a. Alternative f => f a -> f a -> f a <|> [SomeException] -> Maybe SomeException forall a. [a] -> Maybe a listToMaybe [SomeException] otherEx) (([SomeException], [SomeException]) -> Maybe SomeException) -> ([SomeException] -> ([SomeException], [SomeException])) -> [SomeException] -> Maybe SomeException forall b c a. (b -> c) -> (a -> b) -> a -> c . ([Maybe SomeException] -> [SomeException]) -> ([Maybe SomeException], [SomeException]) -> ([SomeException], [SomeException]) forall (p :: * -> * -> *) a b c. Bifunctor p => (a -> b) -> p a c -> p b c first [Maybe SomeException] -> [SomeException] forall a. [Maybe a] -> [a] catMaybes (([Maybe SomeException], [SomeException]) -> ([SomeException], [SomeException])) -> ([SomeException] -> ([Maybe SomeException], [SomeException])) -> [SomeException] -> ([SomeException], [SomeException]) forall b c a. (b -> c) -> (a -> b) -> a -> c . [(Maybe SomeException, SomeException)] -> ([Maybe SomeException], [SomeException]) forall a b. [(a, b)] -> ([a], [b]) unzip ([(Maybe SomeException, SomeException)] -> ([Maybe SomeException], [SomeException])) -> ([SomeException] -> [(Maybe SomeException, SomeException)]) -> [SomeException] -> ([Maybe SomeException], [SomeException]) forall b c a. (b -> c) -> (a -> b) -> a -> c . (SomeException -> (Maybe SomeException, SomeException)) -> [SomeException] -> [(Maybe SomeException, SomeException)] forall a b. (a -> b) -> [a] -> [b] map (\SomeException e -> (SomeException -> Maybe SomeException forall e. Exception e => SomeException -> Maybe e asyncExceptionFromException SomeException e, SomeException e)) -- | Create a new registry -- -- See documentation of 'ResourceRegistry' for a detailed discussion. withRegistry :: (IOLike m, HasCallStack) => (ResourceRegistry m -> m a) -> m a withRegistry :: (ResourceRegistry m -> m a) -> m a withRegistry = m (ResourceRegistry m) -> (ResourceRegistry m -> m ()) -> (ResourceRegistry m -> m a) -> m a forall (m :: * -> *) a b c. MonadThrow m => m a -> (a -> m b) -> (a -> m c) -> m c bracket m (ResourceRegistry m) forall (m :: * -> *). (IOLike m, HasCallStack) => m (ResourceRegistry m) unsafeNewRegistry ResourceRegistry m -> m () forall (m :: * -> *). (IOLike m, HasCallStack) => ResourceRegistry m -> m () 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 :: (IOLike m, HasCallStack) => (ResourceRegistry m -> m a) -> (a -> m ()) -- ^ Release the resource -> (a -> m r) -> m r bracketWithPrivateRegistry :: (ResourceRegistry m -> m a) -> (a -> m ()) -> (a -> m r) -> m r bracketWithPrivateRegistry ResourceRegistry m -> m a newA a -> m () closeA a -> m r body = (ResourceRegistry m -> m r) -> m r forall (m :: * -> *) a. (IOLike m, HasCallStack) => (ResourceRegistry m -> m a) -> m a withRegistry ((ResourceRegistry m -> m r) -> m r) -> (ResourceRegistry m -> m r) -> m r forall a b. (a -> b) -> a -> b $ \ResourceRegistry m registry -> do (ResourceKey m _key, a a) <- ResourceRegistry m -> (ResourceId -> m a) -> (a -> m ()) -> m (ResourceKey m, a) forall (m :: * -> *) a. (IOLike m, HasCallStack) => ResourceRegistry m -> (ResourceId -> m a) -> (a -> m ()) -> m (ResourceKey m, a) allocate ResourceRegistry m registry (\ResourceId _key -> ResourceRegistry m -> m a newA ResourceRegistry m registry) a -> m () closeA a -> m r body a 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 '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 :: (IOLike m, HasCallStack) => WithTempRegistry st m (a, st) -> m a runWithTempRegistry :: WithTempRegistry st m (a, st) -> m a runWithTempRegistry WithTempRegistry st m (a, st) m = (ResourceRegistry m -> m a) -> m a forall (m :: * -> *) a. (IOLike m, HasCallStack) => (ResourceRegistry m -> m a) -> m a withRegistry ((ResourceRegistry m -> m a) -> m a) -> (ResourceRegistry m -> m a) -> m a forall a b. (a -> b) -> a -> b $ \ResourceRegistry m rr -> do StrictTVar m (TransferredTo st) varTransferredTo <- TransferredTo st -> m (StrictTVar m (TransferredTo st)) forall (m :: * -> *) a. (MonadSTM m, HasCallStack, NoThunks a) => a -> m (StrictTVar m a) newTVarIO TransferredTo st forall a. Monoid a => a mempty let tempRegistry :: TempRegistry st m tempRegistry = TempRegistry :: forall st (m :: * -> *). ResourceRegistry m -> StrictTVar m (TransferredTo st) -> TempRegistry st m TempRegistry { tempResourceRegistry :: ResourceRegistry m tempResourceRegistry = ResourceRegistry m rr , tempTransferredTo :: StrictTVar m (TransferredTo st) tempTransferredTo = StrictTVar m (TransferredTo st) varTransferredTo } (a a, st st) <- ReaderT (TempRegistry st m) m (a, st) -> TempRegistry st m -> m (a, st) forall r (m :: * -> *) a. ReaderT r m a -> r -> m a runReaderT (WithTempRegistry st m (a, st) -> ReaderT (TempRegistry st m) m (a, st) forall st (m :: * -> *) a. WithTempRegistry st m a -> ReaderT (TempRegistry st m) m a unWithTempRegistry WithTempRegistry st m (a, st) m) TempRegistry st 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 st transferredTo <- STM m (TransferredTo st) -> m (TransferredTo st) forall (m :: * -> *) a. (MonadSTM m, HasCallStack) => STM m a -> m a atomically (STM m (TransferredTo st) -> m (TransferredTo st)) -> STM m (TransferredTo st) -> m (TransferredTo st) forall a b. (a -> b) -> a -> b $ StrictTVar m (TransferredTo st) -> STM m (TransferredTo st) forall (m :: * -> *) a. MonadSTM m => StrictTVar m a -> STM m a readTVar StrictTVar m (TransferredTo st) varTransferredTo ResourceRegistry m -> TransferredTo st -> st -> m () forall (m :: * -> *) st. IOLike m => ResourceRegistry m -> TransferredTo st -> st -> m () untrackTransferredTo ResourceRegistry m rr TransferredTo st transferredTo st st Context m context <- m (Context m) forall (m :: * -> *). (IOLike m, HasCallStack) => m (Context m) captureContext [Context m] remainingResources <- ResourceRegistry m -> Context m -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] forall (m :: * -> *). IOLike m => ResourceRegistry m -> Context m -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] releaseAllHelper ResourceRegistry m rr Context m context ResourceKey m -> m (Maybe (Context m)) forall (m :: * -> *). (IOLike m, HasCallStack) => ResourceKey m -> m (Maybe (Context m)) release Maybe (Context m) -> (Context m -> m ()) -> m () forall (f :: * -> *) a. Applicative f => Maybe a -> (a -> f ()) -> f () whenJust ([Context m] -> Maybe (Context m) forall a. [a] -> Maybe a listToMaybe [Context m] remainingResources) ((Context m -> m ()) -> m ()) -> (Context m -> m ()) -> m () forall a b. (a -> b) -> a -> b $ \Context m remainingResource -> TempRegistryException -> m () forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a throwIO (TempRegistryException -> m ()) -> TempRegistryException -> m () forall a b. (a -> b) -> a -> b $ TempRegistryRemainingResource :: forall (m :: * -> *). IOLike m => Context m -> Context m -> TempRegistryException TempRegistryRemainingResource { tempRegistryContext :: Context m tempRegistryContext = ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext ResourceRegistry m rr , tempRegistryResource :: Context m tempRegistryResource = Context m remainingResource } a -> m a forall (m :: * -> *) a. Monad m => a -> m a return a a -- | 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. IOLike 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 :: WithTempRegistry innerSt m (a, innerSt, res) -> (res -> m Bool) -> (st -> res -> Bool) -> WithTempRegistry st m a runInnerWithTempRegistry WithTempRegistry innerSt m (a, innerSt, res) inner res -> m Bool free st -> res -> Bool isTransferred = do TempRegistry st m outerTR <- ReaderT (TempRegistry st m) m (TempRegistry st m) -> WithTempRegistry st m (TempRegistry st m) forall st (m :: * -> *) a. ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a WithTempRegistry ReaderT (TempRegistry st m) m (TempRegistry st m) forall r (m :: * -> *). MonadReader r m => m r ask m a -> WithTempRegistry st m a forall (t :: (* -> *) -> * -> *) (m :: * -> *) a. (MonadTrans t, Monad m) => m a -> t m a lift (m a -> WithTempRegistry st m a) -> m a -> WithTempRegistry st m a forall a b. (a -> b) -> a -> b $ WithTempRegistry innerSt m (a, innerSt) -> m a forall (m :: * -> *) st a. (IOLike m, HasCallStack) => WithTempRegistry st m (a, st) -> m a runWithTempRegistry (WithTempRegistry innerSt m (a, innerSt) -> m a) -> WithTempRegistry innerSt m (a, innerSt) -> m a forall a b. (a -> b) -> a -> b $ do (a a, innerSt innerSt, res res) <- WithTempRegistry innerSt m (a, innerSt, res) inner -- Allocate in the outer layer. res _ <- TempRegistry st m -> WithTempRegistry st m res -> WithTempRegistry innerSt m res withFixedTempRegistry TempRegistry st m outerTR (WithTempRegistry st m res -> WithTempRegistry innerSt m res) -> WithTempRegistry st m res -> WithTempRegistry innerSt m res forall a b. (a -> b) -> a -> b $ m res -> (res -> m Bool) -> (st -> res -> Bool) -> WithTempRegistry st m res forall (m :: * -> *) a st. (IOLike m, HasCallStack) => m a -> (a -> m Bool) -> (st -> a -> Bool) -> WithTempRegistry st m a allocateTemp (res -> m res forall (m :: * -> *) a. Monad m => a -> m a return res res) res -> m Bool free st -> res -> Bool 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. (a, innerSt) -> WithTempRegistry innerSt m (a, innerSt) forall (f :: * -> *) a. Applicative f => a -> f a pure (a a, innerSt innerSt) where withFixedTempRegistry :: TempRegistry st m -> WithTempRegistry st m res -> WithTempRegistry innerSt m res withFixedTempRegistry :: TempRegistry st m -> WithTempRegistry st m res -> WithTempRegistry innerSt m res withFixedTempRegistry TempRegistry st m env (WithTempRegistry (ReaderT TempRegistry st m -> m res f)) = ReaderT (TempRegistry innerSt m) m res -> WithTempRegistry innerSt m res forall st (m :: * -> *) a. ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a WithTempRegistry (ReaderT (TempRegistry innerSt m) m res -> WithTempRegistry innerSt m res) -> ReaderT (TempRegistry innerSt m) m res -> WithTempRegistry innerSt m res forall a b. (a -> b) -> a -> b $ (TempRegistry innerSt m -> m res) -> ReaderT (TempRegistry innerSt m) m res forall r (m :: * -> *) a. (r -> m a) -> ReaderT r m a ReaderT ((TempRegistry innerSt m -> m res) -> ReaderT (TempRegistry innerSt m) m res) -> (TempRegistry innerSt m -> m res) -> ReaderT (TempRegistry innerSt m) m res forall a b. (a -> b) -> a -> b $ \TempRegistry innerSt m _ -> TempRegistry st m -> m res f TempRegistry st m 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. IOLike 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 { TransferredTo st -> st -> Set ResourceId runTransferredTo :: st -> Set ResourceId } deriving newtype (b -> TransferredTo st -> TransferredTo st NonEmpty (TransferredTo st) -> TransferredTo st TransferredTo st -> TransferredTo st -> TransferredTo st (TransferredTo st -> TransferredTo st -> TransferredTo st) -> (NonEmpty (TransferredTo st) -> TransferredTo st) -> (forall b. Integral b => b -> TransferredTo st -> TransferredTo st) -> Semigroup (TransferredTo st) forall b. Integral b => b -> TransferredTo st -> TransferredTo st forall st. NonEmpty (TransferredTo st) -> TransferredTo st forall st. TransferredTo st -> TransferredTo st -> TransferredTo st forall a. (a -> a -> a) -> (NonEmpty a -> a) -> (forall b. Integral b => b -> a -> a) -> Semigroup a forall st b. Integral b => b -> TransferredTo st -> TransferredTo st stimes :: b -> TransferredTo st -> TransferredTo st $cstimes :: forall st b. Integral b => b -> TransferredTo st -> TransferredTo st sconcat :: NonEmpty (TransferredTo st) -> TransferredTo st $csconcat :: forall st. NonEmpty (TransferredTo st) -> TransferredTo st <> :: TransferredTo st -> TransferredTo st -> TransferredTo st $c<> :: forall st. TransferredTo st -> TransferredTo st -> TransferredTo st Semigroup, Semigroup (TransferredTo st) TransferredTo st Semigroup (TransferredTo st) -> TransferredTo st -> (TransferredTo st -> TransferredTo st -> TransferredTo st) -> ([TransferredTo st] -> TransferredTo st) -> Monoid (TransferredTo st) [TransferredTo st] -> TransferredTo st TransferredTo st -> TransferredTo st -> TransferredTo st forall st. Semigroup (TransferredTo st) forall st. TransferredTo st forall a. Semigroup a -> a -> (a -> a -> a) -> ([a] -> a) -> Monoid a forall st. [TransferredTo st] -> TransferredTo st forall st. TransferredTo st -> TransferredTo st -> TransferredTo st mconcat :: [TransferredTo st] -> TransferredTo st $cmconcat :: forall st. [TransferredTo st] -> TransferredTo st mappend :: TransferredTo st -> TransferredTo st -> TransferredTo st $cmappend :: forall st. TransferredTo st -> TransferredTo st -> TransferredTo st mempty :: TransferredTo st $cmempty :: forall st. TransferredTo st $cp1Monoid :: forall st. Semigroup (TransferredTo st) Monoid) deriving Context -> TransferredTo st -> IO (Maybe ThunkInfo) Proxy (TransferredTo st) -> String (Context -> TransferredTo st -> IO (Maybe ThunkInfo)) -> (Context -> TransferredTo st -> IO (Maybe ThunkInfo)) -> (Proxy (TransferredTo st) -> String) -> NoThunks (TransferredTo st) forall st. Context -> TransferredTo st -> IO (Maybe ThunkInfo) forall st. Proxy (TransferredTo st) -> String forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a showTypeOf :: Proxy (TransferredTo st) -> String $cshowTypeOf :: forall st. Proxy (TransferredTo st) -> String wNoThunks :: Context -> TransferredTo st -> IO (Maybe ThunkInfo) $cwNoThunks :: forall st. Context -> TransferredTo st -> IO (Maybe ThunkInfo) noThunks :: Context -> TransferredTo st -> IO (Maybe ThunkInfo) $cnoThunks :: forall st. Context -> TransferredTo st -> IO (Maybe ThunkInfo) NoThunks via OnlyCheckWhnfNamed "TransferredTo" (TransferredTo st) -- | The environment used to run a 'WithTempRegistry' action. data TempRegistry st m = TempRegistry { TempRegistry st m -> ResourceRegistry m tempResourceRegistry :: !(ResourceRegistry m) , TempRegistry st m -> StrictTVar m (TransferredTo st) 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 { WithTempRegistry st m a -> ReaderT (TempRegistry st m) m a unWithTempRegistry :: ReaderT (TempRegistry st m) m a } deriving newtype (a -> WithTempRegistry st m b -> WithTempRegistry st m a (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b (forall a b. (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b) -> (forall a b. a -> WithTempRegistry st m b -> WithTempRegistry st m a) -> Functor (WithTempRegistry st m) forall a b. a -> WithTempRegistry st m b -> WithTempRegistry st m a forall a b. (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b forall st (m :: * -> *) a b. Functor m => a -> WithTempRegistry st m b -> WithTempRegistry st m a forall st (m :: * -> *) a b. Functor m => (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b forall (f :: * -> *). (forall a b. (a -> b) -> f a -> f b) -> (forall a b. a -> f b -> f a) -> Functor f <$ :: a -> WithTempRegistry st m b -> WithTempRegistry st m a $c<$ :: forall st (m :: * -> *) a b. Functor m => a -> WithTempRegistry st m b -> WithTempRegistry st m a fmap :: (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b $cfmap :: forall st (m :: * -> *) a b. Functor m => (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b Functor, Functor (WithTempRegistry st m) a -> WithTempRegistry st m a Functor (WithTempRegistry st m) -> (forall a. a -> WithTempRegistry st m a) -> (forall a b. WithTempRegistry st m (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b) -> (forall a b c. (a -> b -> c) -> WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c) -> (forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b) -> (forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a) -> Applicative (WithTempRegistry st m) WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a WithTempRegistry st m (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b (a -> b -> c) -> WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c forall a. a -> WithTempRegistry st m a forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b forall a b. WithTempRegistry st m (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b forall a b c. (a -> b -> c) -> WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c forall st (m :: * -> *). Applicative m => Functor (WithTempRegistry st m) forall st (m :: * -> *) a. Applicative m => a -> WithTempRegistry st m a forall st (m :: * -> *) a b. Applicative m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a forall st (m :: * -> *) a b. Applicative m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b forall st (m :: * -> *) a b. Applicative m => WithTempRegistry st m (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b forall st (m :: * -> *) a b c. Applicative m => (a -> b -> c) -> WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c forall (f :: * -> *). Functor f -> (forall a. a -> f a) -> (forall a b. f (a -> b) -> f a -> f b) -> (forall a b c. (a -> b -> c) -> f a -> f b -> f c) -> (forall a b. f a -> f b -> f b) -> (forall a b. f a -> f b -> f a) -> Applicative f <* :: WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a $c<* :: forall st (m :: * -> *) a b. Applicative m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a *> :: WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b $c*> :: forall st (m :: * -> *) a b. Applicative m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b liftA2 :: (a -> b -> c) -> WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c $cliftA2 :: forall st (m :: * -> *) a b c. Applicative m => (a -> b -> c) -> WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c <*> :: WithTempRegistry st m (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b $c<*> :: forall st (m :: * -> *) a b. Applicative m => WithTempRegistry st m (a -> b) -> WithTempRegistry st m a -> WithTempRegistry st m b pure :: a -> WithTempRegistry st m a $cpure :: forall st (m :: * -> *) a. Applicative m => a -> WithTempRegistry st m a $cp1Applicative :: forall st (m :: * -> *). Applicative m => Functor (WithTempRegistry st m) Applicative, Applicative (WithTempRegistry st m) a -> WithTempRegistry st m a Applicative (WithTempRegistry st m) -> (forall a b. WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m b) -> (forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b) -> (forall a. a -> WithTempRegistry st m a) -> Monad (WithTempRegistry st m) WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m b WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b forall a. a -> WithTempRegistry st m a forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b forall a b. WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m b forall st (m :: * -> *). Monad m => Applicative (WithTempRegistry st m) forall st (m :: * -> *) a. Monad m => a -> WithTempRegistry st m a forall st (m :: * -> *) a b. Monad m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b forall st (m :: * -> *) a b. Monad m => WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m b forall (m :: * -> *). Applicative m -> (forall a b. m a -> (a -> m b) -> m b) -> (forall a b. m a -> m b -> m b) -> (forall a. a -> m a) -> Monad m return :: a -> WithTempRegistry st m a $creturn :: forall st (m :: * -> *) a. Monad m => a -> WithTempRegistry st m a >> :: WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b $c>> :: forall st (m :: * -> *) a b. Monad m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m b >>= :: WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m b $c>>= :: forall st (m :: * -> *) a b. Monad m => WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m b $cp1Monad :: forall st (m :: * -> *). Monad m => Applicative (WithTempRegistry st m) Monad, Monad (WithTempRegistry st m) e -> WithTempRegistry st m a Monad (WithTempRegistry st m) -> (forall e a. Exception e => e -> WithTempRegistry st m a) -> (forall a b c. WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c) -> (forall a b c. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c -> WithTempRegistry st m c) -> (forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a) -> MonadThrow (WithTempRegistry st m) WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c -> WithTempRegistry st m c WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a forall e a. Exception e => e -> WithTempRegistry st m a forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a forall a b c. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c -> WithTempRegistry st m c forall a b c. WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c forall st (m :: * -> *). MonadThrow m => Monad (WithTempRegistry st m) forall st (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> WithTempRegistry st m a forall st (m :: * -> *) a b. MonadThrow m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a forall st (m :: * -> *) a b c. MonadThrow m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c -> WithTempRegistry st m c forall st (m :: * -> *) a b c. MonadThrow m => WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c forall (m :: * -> *). Monad m -> (forall e a. Exception e => e -> m a) -> (forall a b c. m a -> (a -> m b) -> (a -> m c) -> m c) -> (forall a b c. m a -> m b -> m c -> m c) -> (forall a b. m a -> m b -> m a) -> MonadThrow m finally :: WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a $cfinally :: forall st (m :: * -> *) a b. MonadThrow m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a bracket_ :: WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c -> WithTempRegistry st m c $cbracket_ :: forall st (m :: * -> *) a b c. MonadThrow m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m c -> WithTempRegistry st m c bracket :: WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c $cbracket :: forall st (m :: * -> *) a b c. MonadThrow m => WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c throwIO :: e -> WithTempRegistry st m a $cthrowIO :: forall st (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> WithTempRegistry st m a $cp1MonadThrow :: forall st (m :: * -> *). MonadThrow m => Monad (WithTempRegistry st m) MonadThrow, MonadThrow (WithTempRegistry st m) MonadThrow (WithTempRegistry st m) -> (forall e a. Exception e => WithTempRegistry st m a -> (e -> WithTempRegistry st m a) -> WithTempRegistry st m a) -> (forall e b a. Exception e => (e -> Maybe b) -> WithTempRegistry st m a -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a) -> (forall e a. Exception e => WithTempRegistry st m a -> WithTempRegistry st m (Either e a)) -> (forall e b a. Exception e => (e -> Maybe b) -> WithTempRegistry st m a -> WithTempRegistry st m (Either b a)) -> (forall e a. Exception e => (e -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a) -> (forall e b a. Exception e => (e -> Maybe b) -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a) -> (forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a) -> (forall a b c. WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c) -> (forall a b c. WithTempRegistry st m a -> (a -> ExitCase b -> WithTempRegistry st m c) -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m (b, c)) -> MonadCatch (WithTempRegistry st m) WithTempRegistry st m a -> (e -> WithTempRegistry st m a) -> WithTempRegistry st m a WithTempRegistry st m a -> WithTempRegistry st m (Either e a) WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c WithTempRegistry st m a -> (a -> ExitCase b -> WithTempRegistry st m c) -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m (b, c) (e -> Maybe b) -> WithTempRegistry st m a -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a (e -> Maybe b) -> WithTempRegistry st m a -> WithTempRegistry st m (Either b a) (e -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a (e -> Maybe b) -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a forall e a. Exception e => WithTempRegistry st m a -> WithTempRegistry st m (Either e a) forall e a. Exception e => WithTempRegistry st m a -> (e -> WithTempRegistry st m a) -> WithTempRegistry st m a forall e a. Exception e => (e -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a forall a b. WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a forall e b a. Exception e => (e -> Maybe b) -> WithTempRegistry st m a -> WithTempRegistry st m (Either b a) forall e b a. Exception e => (e -> Maybe b) -> WithTempRegistry st m a -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a forall e b a. Exception e => (e -> Maybe b) -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a forall a b c. WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c forall a b c. WithTempRegistry st m a -> (a -> ExitCase b -> WithTempRegistry st m c) -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m (b, c) forall st (m :: * -> *). MonadCatch m => MonadThrow (WithTempRegistry st m) forall st (m :: * -> *) e a. (MonadCatch m, Exception e) => WithTempRegistry st m a -> WithTempRegistry st m (Either e a) forall st (m :: * -> *) e a. (MonadCatch m, Exception e) => WithTempRegistry st m a -> (e -> WithTempRegistry st m a) -> WithTempRegistry st m a forall st (m :: * -> *) e a. (MonadCatch m, Exception e) => (e -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a forall st (m :: * -> *) a b. MonadCatch m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a forall st (m :: * -> *) e b a. (MonadCatch m, Exception e) => (e -> Maybe b) -> WithTempRegistry st m a -> WithTempRegistry st m (Either b a) forall st (m :: * -> *) e b a. (MonadCatch m, Exception e) => (e -> Maybe b) -> WithTempRegistry st m a -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a forall st (m :: * -> *) e b a. (MonadCatch m, Exception e) => (e -> Maybe b) -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a forall st (m :: * -> *) a b c. MonadCatch m => WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c forall st (m :: * -> *) a b c. MonadCatch m => WithTempRegistry st m a -> (a -> ExitCase b -> WithTempRegistry st m c) -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m (b, c) forall (m :: * -> *). MonadThrow m -> (forall e a. Exception e => m a -> (e -> m a) -> m a) -> (forall e b a. Exception e => (e -> Maybe b) -> m a -> (b -> m a) -> m a) -> (forall e a. Exception e => m a -> m (Either e a)) -> (forall e b a. Exception e => (e -> Maybe b) -> m a -> m (Either b a)) -> (forall e a. Exception e => (e -> m a) -> m a -> m a) -> (forall e b a. Exception e => (e -> Maybe b) -> (b -> m a) -> m a -> m a) -> (forall a b. m a -> m b -> m a) -> (forall a b c. m a -> (a -> m b) -> (a -> m c) -> m c) -> (forall a b c. m a -> (a -> ExitCase b -> m c) -> (a -> m b) -> m (b, c)) -> MonadCatch m generalBracket :: WithTempRegistry st m a -> (a -> ExitCase b -> WithTempRegistry st m c) -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m (b, c) $cgeneralBracket :: forall st (m :: * -> *) a b c. MonadCatch m => WithTempRegistry st m a -> (a -> ExitCase b -> WithTempRegistry st m c) -> (a -> WithTempRegistry st m b) -> WithTempRegistry st m (b, c) bracketOnError :: WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c $cbracketOnError :: forall st (m :: * -> *) a b c. MonadCatch m => WithTempRegistry st m a -> (a -> WithTempRegistry st m b) -> (a -> WithTempRegistry st m c) -> WithTempRegistry st m c onException :: WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a $conException :: forall st (m :: * -> *) a b. MonadCatch m => WithTempRegistry st m a -> WithTempRegistry st m b -> WithTempRegistry st m a handleJust :: (e -> Maybe b) -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a $chandleJust :: forall st (m :: * -> *) e b a. (MonadCatch m, Exception e) => (e -> Maybe b) -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a handle :: (e -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a $chandle :: forall st (m :: * -> *) e a. (MonadCatch m, Exception e) => (e -> WithTempRegistry st m a) -> WithTempRegistry st m a -> WithTempRegistry st m a tryJust :: (e -> Maybe b) -> WithTempRegistry st m a -> WithTempRegistry st m (Either b a) $ctryJust :: forall st (m :: * -> *) e b a. (MonadCatch m, Exception e) => (e -> Maybe b) -> WithTempRegistry st m a -> WithTempRegistry st m (Either b a) try :: WithTempRegistry st m a -> WithTempRegistry st m (Either e a) $ctry :: forall st (m :: * -> *) e a. (MonadCatch m, Exception e) => WithTempRegistry st m a -> WithTempRegistry st m (Either e a) catchJust :: (e -> Maybe b) -> WithTempRegistry st m a -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a $ccatchJust :: forall st (m :: * -> *) e b a. (MonadCatch m, Exception e) => (e -> Maybe b) -> WithTempRegistry st m a -> (b -> WithTempRegistry st m a) -> WithTempRegistry st m a catch :: WithTempRegistry st m a -> (e -> WithTempRegistry st m a) -> WithTempRegistry st m a $ccatch :: forall st (m :: * -> *) e a. (MonadCatch m, Exception e) => WithTempRegistry st m a -> (e -> WithTempRegistry st m a) -> WithTempRegistry st m a $cp1MonadCatch :: forall st (m :: * -> *). MonadCatch m => MonadThrow (WithTempRegistry st m) MonadCatch, MonadCatch (WithTempRegistry st m) MonadCatch (WithTempRegistry st m) -> (forall b. ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b) -> (forall b. ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b) -> (forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> (forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> MonadMask (WithTempRegistry st m) WithTempRegistry st m a -> WithTempRegistry st m a WithTempRegistry st m a -> WithTempRegistry st m a ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b forall a. WithTempRegistry st m a -> WithTempRegistry st m a forall b. ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b forall st (m :: * -> *). MonadMask m => MonadCatch (WithTempRegistry st m) forall st (m :: * -> *) a. MonadMask m => WithTempRegistry st m a -> WithTempRegistry st m a forall st (m :: * -> *) b. MonadMask m => ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b forall (m :: * -> *). MonadCatch m -> (forall b. ((forall a. m a -> m a) -> m b) -> m b) -> (forall b. ((forall a. m a -> m a) -> m b) -> m b) -> (forall a. m a -> m a) -> (forall a. m a -> m a) -> MonadMask m uninterruptibleMask_ :: WithTempRegistry st m a -> WithTempRegistry st m a $cuninterruptibleMask_ :: forall st (m :: * -> *) a. MonadMask m => WithTempRegistry st m a -> WithTempRegistry st m a mask_ :: WithTempRegistry st m a -> WithTempRegistry st m a $cmask_ :: forall st (m :: * -> *) a. MonadMask m => WithTempRegistry st m a -> WithTempRegistry st m a uninterruptibleMask :: ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b $cuninterruptibleMask :: forall st (m :: * -> *) b. MonadMask m => ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b mask :: ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b $cmask :: forall st (m :: * -> *) b. MonadMask m => ((forall a. WithTempRegistry st m a -> WithTempRegistry st m a) -> WithTempRegistry st m b) -> WithTempRegistry st m b $cp1MonadMask :: forall st (m :: * -> *). MonadMask m => MonadCatch (WithTempRegistry st m) MonadMask) instance MonadTrans (WithTempRegistry st) where lift :: m a -> WithTempRegistry st m a lift = ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a forall st (m :: * -> *) a. ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a WithTempRegistry (ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a) -> (m a -> ReaderT (TempRegistry st m) m a) -> m a -> WithTempRegistry st m a forall b c a. (b -> c) -> (a -> b) -> a -> c . m a -> ReaderT (TempRegistry st m) m a forall (t :: (* -> *) -> * -> *) (m :: * -> *) a. (MonadTrans t, Monad m) => m a -> t m a lift instance MonadState s m => MonadState s (WithTempRegistry st m) where state :: (s -> (a, s)) -> WithTempRegistry st m a state = ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a forall st (m :: * -> *) a. ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a WithTempRegistry (ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a) -> ((s -> (a, s)) -> ReaderT (TempRegistry st m) m a) -> (s -> (a, s)) -> WithTempRegistry st m a forall b c a. (b -> c) -> (a -> b) -> a -> c . (s -> (a, s)) -> ReaderT (TempRegistry st m) m a forall s (m :: * -> *) a. MonadState s m => (s -> (a, s)) -> m a 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 :: IOLike m => ResourceRegistry m -> TransferredTo st -> st -> m () untrackTransferredTo :: ResourceRegistry m -> TransferredTo st -> st -> m () untrackTransferredTo ResourceRegistry m rr TransferredTo st transferredTo st st = ResourceRegistry m -> State (RegistryState m) () -> m () forall (m :: * -> *) a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr (State (RegistryState m) () -> m ()) -> State (RegistryState m) () -> m () forall a b. (a -> b) -> a -> b $ (ResourceId -> StateT (RegistryState m) Identity (Maybe (Resource m))) -> Set ResourceId -> State (RegistryState m) () forall (t :: * -> *) (m :: * -> *) a b. (Foldable t, Monad m) => (a -> m b) -> t a -> m () mapM_ ResourceId -> StateT (RegistryState m) Identity (Maybe (Resource m)) forall (m :: * -> *). ResourceId -> State (RegistryState m) (Maybe (Resource m)) removeResource Set ResourceId rids where rids :: Set ResourceId rids = TransferredTo st -> st -> Set ResourceId forall st. TransferredTo st -> st -> Set ResourceId runTransferredTo TransferredTo st transferredTo st st -- | Allocate a resource in a temporary registry until it has been transferred -- to the final state @st@. See 'runWithTempRegistry' for more details. allocateTemp :: (IOLike 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 :: m a -> (a -> m Bool) -> (st -> a -> Bool) -> WithTempRegistry st m a allocateTemp m a alloc a -> m Bool free st -> a -> Bool isTransferred = ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a forall st (m :: * -> *) a. ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a WithTempRegistry (ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a) -> ReaderT (TempRegistry st m) m a -> WithTempRegistry st m a forall a b. (a -> b) -> a -> b $ do TempRegistry ResourceRegistry m rr StrictTVar m (TransferredTo st) varTransferredTo <- ReaderT (TempRegistry st m) m (TempRegistry st m) forall r (m :: * -> *). MonadReader r m => m r ask (ResourceKey m key, a a) <- m (ResourceKey m, a) -> ReaderT (TempRegistry st m) m (ResourceKey m, a) forall (t :: (* -> *) -> * -> *) (m :: * -> *) a. (MonadTrans t, Monad m) => m a -> t m a lift (m (ResourceKey m, a) -> ReaderT (TempRegistry st m) m (ResourceKey m, a)) -> m (ResourceKey m, a) -> ReaderT (TempRegistry st m) m (ResourceKey m, a) forall a b. (a -> b) -> a -> b $ (Either Void (ResourceKey m, a) -> (ResourceKey m, a)) -> m (Either Void (ResourceKey m, a)) -> m (ResourceKey m, a) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap Either Void (ResourceKey m, a) -> (ResourceKey m, a) forall a. Either Void a -> a mustBeRight (m (Either Void (ResourceKey m, a)) -> m (ResourceKey m, a)) -> m (Either Void (ResourceKey m, a)) -> m (ResourceKey m, a) forall a b. (a -> b) -> a -> b $ ResourceRegistry m -> (ResourceId -> m (Either Void a)) -> (a -> m Bool) -> m (Either Void (ResourceKey m, a)) forall (m :: * -> *) e a. (IOLike m, HasCallStack) => ResourceRegistry m -> (ResourceId -> m (Either e a)) -> (a -> m Bool) -> m (Either e (ResourceKey m, a)) allocateEither ResourceRegistry m rr ((a -> Either Void a) -> m a -> m (Either Void a) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap a -> Either Void a forall a b. b -> Either a b Right (m a -> m (Either Void a)) -> (ResourceId -> m a) -> ResourceId -> m (Either Void a) forall b c a. (b -> c) -> (a -> b) -> a -> c . m a -> ResourceId -> m a forall a b. a -> b -> a const m a alloc) a -> m Bool free m () -> ReaderT (TempRegistry st m) m () forall (t :: (* -> *) -> * -> *) (m :: * -> *) a. (MonadTrans t, Monad m) => m a -> t m a lift (m () -> ReaderT (TempRegistry st m) m ()) -> m () -> ReaderT (TempRegistry st m) m () forall a b. (a -> b) -> a -> b $ STM m () -> m () forall (m :: * -> *) a. (MonadSTM m, HasCallStack) => STM m a -> m a atomically (STM m () -> m ()) -> STM m () -> m () forall a b. (a -> b) -> a -> b $ StrictTVar m (TransferredTo st) -> (TransferredTo st -> TransferredTo st) -> STM m () forall (m :: * -> *) a. MonadSTM m => StrictTVar m a -> (a -> a) -> STM m () modifyTVar StrictTVar m (TransferredTo st) varTransferredTo ((TransferredTo st -> TransferredTo st) -> STM m ()) -> (TransferredTo st -> TransferredTo st) -> STM m () forall a b. (a -> b) -> a -> b $ TransferredTo st -> TransferredTo st -> TransferredTo st forall a. Monoid a => a -> a -> a mappend (TransferredTo st -> TransferredTo st -> TransferredTo st) -> TransferredTo st -> TransferredTo st -> TransferredTo st forall a b. (a -> b) -> a -> b $ (st -> Set ResourceId) -> TransferredTo st forall st. (st -> Set ResourceId) -> TransferredTo st TransferredTo ((st -> Set ResourceId) -> TransferredTo st) -> (st -> Set ResourceId) -> TransferredTo st forall a b. (a -> b) -> a -> b $ \st st -> if st -> a -> Bool isTransferred st st a a then ResourceId -> Set ResourceId forall a. a -> Set a Set.singleton (ResourceKey m -> ResourceId forall (m :: * -> *). ResourceKey m -> ResourceId resourceKeyId ResourceKey m key) else Set ResourceId forall a. Set a Set.empty a -> ReaderT (TempRegistry st m) m a forall (m :: * -> *) a. Monad m => a -> m a return a 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. IOLike 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 :: m st -> (st -> ExitCase st -> m ()) -> StateT st (WithTempRegistry st m) a -> m a modifyWithTempRegistry m st getSt st -> ExitCase st -> m () putSt StateT st (WithTempRegistry st m) a modSt = WithTempRegistry st m (a, st) -> m a forall (m :: * -> *) st a. (IOLike m, HasCallStack) => WithTempRegistry st m (a, st) -> m a runWithTempRegistry (WithTempRegistry st m (a, st) -> m a) -> WithTempRegistry st m (a, st) -> m a forall a b. (a -> b) -> a -> b $ ((a, st), ()) -> (a, st) forall a b. (a, b) -> a fst (((a, st), ()) -> (a, st)) -> WithTempRegistry st m ((a, st), ()) -> WithTempRegistry st m (a, st) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> WithTempRegistry st m st -> (st -> ExitCase (a, st) -> WithTempRegistry st m ()) -> (st -> WithTempRegistry st m (a, st)) -> WithTempRegistry st m ((a, st), ()) forall (m :: * -> *) a b c. MonadCatch m => m a -> (a -> ExitCase b -> m c) -> (a -> m b) -> m (b, c) generalBracket (m st -> WithTempRegistry st m st forall (t :: (* -> *) -> * -> *) (m :: * -> *) a. (MonadTrans t, Monad m) => m a -> t m a lift m st getSt) st -> ExitCase (a, st) -> WithTempRegistry st m () transfer st -> WithTempRegistry st m (a, st) mutate where transfer :: st -> ExitCase (a, st) -> WithTempRegistry st m () transfer :: st -> ExitCase (a, st) -> WithTempRegistry st m () transfer st initSt ExitCase (a, st) ec = m () -> WithTempRegistry st m () forall (t :: (* -> *) -> * -> *) (m :: * -> *) a. (MonadTrans t, Monad m) => m a -> t m a lift (m () -> WithTempRegistry st m ()) -> m () -> WithTempRegistry st m () forall a b. (a -> b) -> a -> b $ st -> ExitCase st -> m () putSt st initSt ((a, st) -> st forall a b. (a, b) -> b snd ((a, st) -> st) -> ExitCase (a, st) -> ExitCase st forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> ExitCase (a, st) ec) mutate :: st -> WithTempRegistry st m (a, st) mutate :: st -> WithTempRegistry st m (a, st) mutate = StateT st (WithTempRegistry st m) a -> st -> WithTempRegistry st m (a, st) forall s (m :: * -> *) a. StateT s m a -> s -> m (a, s) runStateT StateT st (WithTempRegistry st m) a modSt {------------------------------------------------------------------------------- Simple queries on the registry -------------------------------------------------------------------------------} -- | The thread that created the registry registryThread :: ResourceRegistry m -> ThreadId m registryThread :: ResourceRegistry m -> ThreadId m registryThread = Context m -> ThreadId m forall (m :: * -> *). Context m -> ThreadId m contextThreadId (Context m -> ThreadId m) -> (ResourceRegistry m -> Context m) -> ResourceRegistry m -> ThreadId m forall b c a. (b -> c) -> (a -> b) -> a -> c . ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext -- | Number of currently allocated resources -- -- Primarily for the benefit of testing. countResources :: IOLike m => ResourceRegistry m -> m Int countResources :: ResourceRegistry m -> m Int countResources ResourceRegistry m rr = STM m Int -> m Int forall (m :: * -> *) a. (MonadSTM m, HasCallStack) => STM m a -> m a atomically (STM m Int -> m Int) -> STM m Int -> m Int forall a b. (a -> b) -> a -> b $ RegistryState m -> Int forall (m :: * -> *). RegistryState m -> Int aux (RegistryState m -> Int) -> STM m (RegistryState m) -> STM m Int forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> StrictTVar m (RegistryState m) -> STM m (RegistryState m) forall (m :: * -> *) a. MonadSTM m => StrictTVar m a -> STM m a readTVar (ResourceRegistry m -> StrictTVar m (RegistryState m) forall (m :: * -> *). ResourceRegistry m -> StrictTVar m (RegistryState m) registryState ResourceRegistry m rr) where aux :: RegistryState m -> Int aux :: RegistryState m -> Int aux = Map ResourceId (Resource m) -> Int forall k a. Map k a -> Int Map.size (Map ResourceId (Resource m) -> Int) -> (RegistryState m -> Map ResourceId (Resource m)) -> RegistryState m -> Int forall b c a. (b -> c) -> (a -> b) -> a -> c . RegistryState m -> Map ResourceId (Resource m) forall (m :: * -> *). RegistryState m -> Map ResourceId (Resource m) 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. (IOLike m, HasCallStack) => ResourceRegistry m -> (ResourceId -> m a) -> (a -> m ()) -- ^ Release the resource -> m (ResourceKey m, a) allocate :: ResourceRegistry m -> (ResourceId -> m a) -> (a -> m ()) -> m (ResourceKey m, a) allocate ResourceRegistry m rr ResourceId -> m a alloc a -> m () free = Either Void (ResourceKey m, a) -> (ResourceKey m, a) forall a. Either Void a -> a mustBeRight (Either Void (ResourceKey m, a) -> (ResourceKey m, a)) -> m (Either Void (ResourceKey m, a)) -> m (ResourceKey m, a) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> ResourceRegistry m -> (ResourceId -> m (Either Void a)) -> (a -> m Bool) -> m (Either Void (ResourceKey m, a)) forall (m :: * -> *) e a. (IOLike m, HasCallStack) => ResourceRegistry m -> (ResourceId -> m (Either e a)) -> (a -> m Bool) -> m (Either e (ResourceKey m, a)) allocateEither ResourceRegistry m rr ((a -> Either Void a) -> m a -> m (Either Void a) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap a -> Either Void a forall a b. b -> Either a b Right (m a -> m (Either Void a)) -> (ResourceId -> m a) -> ResourceId -> m (Either Void a) forall b c a. (b -> c) -> (a -> b) -> a -> c . ResourceId -> m a alloc) (\a a -> a -> m () free a a m () -> m Bool -> m Bool forall (m :: * -> *) a b. Monad m => m a -> m b -> m b >> Bool -> m Bool forall (m :: * -> *) a. Monad m => a -> m a return Bool True) -- | Generalization of 'allocate' for allocation functions that may fail allocateEither :: forall m e a. (IOLike 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 :: ResourceRegistry m -> (ResourceId -> m (Either e a)) -> (a -> m Bool) -> m (Either e (ResourceKey m, a)) allocateEither ResourceRegistry m rr ResourceId -> m (Either e a) alloc a -> m Bool free = do Context m context <- m (Context m) forall (m :: * -> *). (IOLike m, HasCallStack) => m (Context m) captureContext ResourceRegistry m -> Context m -> m () forall (m :: * -> *). IOLike m => ResourceRegistry m -> Context m -> m () ensureKnownThread ResourceRegistry m rr Context m context -- We check if the registry has been closed when we allocate the key, so -- that we can avoid needlessly allocating the resource. Either PrettyCallStack ResourceId mKey <- ResourceRegistry m -> State (RegistryState m) (Either PrettyCallStack ResourceId) -> m (Either PrettyCallStack ResourceId) forall (m :: * -> *) a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr (State (RegistryState m) (Either PrettyCallStack ResourceId) -> m (Either PrettyCallStack ResourceId)) -> State (RegistryState m) (Either PrettyCallStack ResourceId) -> m (Either PrettyCallStack ResourceId) forall a b. (a -> b) -> a -> b $ State (RegistryState m) (Either PrettyCallStack ResourceId) forall (m :: * -> *). State (RegistryState m) (Either PrettyCallStack ResourceId) allocKey case Either PrettyCallStack ResourceId mKey of Left PrettyCallStack closed -> ResourceRegistry m -> Context m -> PrettyCallStack -> m (Either e (ResourceKey m, a)) forall (m :: * -> *) x. IOLike m => ResourceRegistry m -> Context m -> PrettyCallStack -> m x throwRegistryClosed ResourceRegistry m rr Context m context PrettyCallStack closed Right ResourceId key -> m (Either e (ResourceKey m, a)) -> m (Either e (ResourceKey m, a)) forall (m :: * -> *) a. MonadMask m => m a -> m a mask_ (m (Either e (ResourceKey m, a)) -> m (Either e (ResourceKey m, a))) -> m (Either e (ResourceKey m, a)) -> m (Either e (ResourceKey m, a)) forall a b. (a -> b) -> a -> b $ do Either e a ma <- ResourceId -> m (Either e a) alloc ResourceId key case Either e a ma of Left e e -> Either e (ResourceKey m, a) -> m (Either e (ResourceKey m, a)) forall (m :: * -> *) a. Monad m => a -> m a return (Either e (ResourceKey m, a) -> m (Either e (ResourceKey m, a))) -> Either e (ResourceKey m, a) -> m (Either e (ResourceKey m, a)) forall a b. (a -> b) -> a -> b $ e -> Either e (ResourceKey m, a) forall a b. a -> Either a b Left e e Right a a -> do -- TODO: Might want to have an exception handler around this call to -- 'updateState' just in case /that/ throws an exception. Either PrettyCallStack () inserted <- ResourceRegistry m -> State (RegistryState m) (Either PrettyCallStack ()) -> m (Either PrettyCallStack ()) forall (m :: * -> *) a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr (State (RegistryState m) (Either PrettyCallStack ()) -> m (Either PrettyCallStack ())) -> State (RegistryState m) (Either PrettyCallStack ()) -> m (Either PrettyCallStack ()) forall a b. (a -> b) -> a -> b $ ResourceId -> Resource m -> State (RegistryState m) (Either PrettyCallStack ()) forall (m :: * -> *). ResourceId -> Resource m -> State (RegistryState m) (Either PrettyCallStack ()) insertResource ResourceId key (Context m -> a -> Resource m mkResource Context m context a a) case Either PrettyCallStack () inserted of Left PrettyCallStack 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. m Bool -> m () forall (f :: * -> *) a. Functor f => f a -> f () void (m Bool -> m ()) -> m Bool -> m () forall a b. (a -> b) -> a -> b $ a -> m Bool free a a ResourceRegistry m -> Context m -> PrettyCallStack -> m (Either e (ResourceKey m, a)) forall (m :: * -> *) x. IOLike m => ResourceRegistry m -> Context m -> PrettyCallStack -> m x throwRegistryClosed ResourceRegistry m rr Context m context PrettyCallStack closed Right () -> Either e (ResourceKey m, a) -> m (Either e (ResourceKey m, a)) forall (m :: * -> *) a. Monad m => a -> m a return (Either e (ResourceKey m, a) -> m (Either e (ResourceKey m, a))) -> Either e (ResourceKey m, a) -> m (Either e (ResourceKey m, a)) forall a b. (a -> b) -> a -> b $ (ResourceKey m, a) -> Either e (ResourceKey m, a) forall a b. b -> Either a b Right (ResourceRegistry m -> ResourceId -> ResourceKey m forall (m :: * -> *). ResourceRegistry m -> ResourceId -> ResourceKey m ResourceKey ResourceRegistry m rr ResourceId key, a a) where mkResource :: Context m -> a -> Resource m mkResource :: Context m -> a -> Resource m mkResource Context m context a a = Resource :: forall (m :: * -> *). Context m -> Release m -> Resource m Resource { resourceContext :: Context m resourceContext = Context m context , resourceRelease :: Release m resourceRelease = m Bool -> Release m forall (m :: * -> *). m Bool -> Release m Release (m Bool -> Release m) -> m Bool -> Release m forall a b. (a -> b) -> a -> b $ a -> m Bool free a a } throwRegistryClosed :: IOLike m => ResourceRegistry m -> Context m -> PrettyCallStack -> m x throwRegistryClosed :: ResourceRegistry m -> Context m -> PrettyCallStack -> m x throwRegistryClosed ResourceRegistry m rr Context m context PrettyCallStack closed = RegistryClosedException -> m x forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a throwIO RegistryClosedException :: forall (m :: * -> *). IOLike m => Context m -> PrettyCallStack -> Context m -> RegistryClosedException RegistryClosedException { registryClosedRegistryContext :: Context m registryClosedRegistryContext = ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext ResourceRegistry m rr , registryClosedCloseCallStack :: PrettyCallStack registryClosedCloseCallStack = PrettyCallStack closed , registryClosedAllocContext :: Context m registryClosedAllocContext = Context m 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 :: (IOLike m, HasCallStack) => ResourceKey m -> m (Maybe (Context m)) release :: ResourceKey m -> m (Maybe (Context m)) release key :: ResourceKey m key@(ResourceKey ResourceRegistry m rr ResourceId _) = do Context m context <- m (Context m) forall (m :: * -> *). (IOLike m, HasCallStack) => m (Context m) captureContext ResourceRegistry m -> Context m -> m () forall (m :: * -> *). IOLike m => ResourceRegistry m -> Context m -> m () ensureKnownThread ResourceRegistry m rr Context m context ResourceKey m -> m (Maybe (Context m)) forall (m :: * -> *). IOLike m => ResourceKey m -> m (Maybe (Context m)) unsafeRelease ResourceKey m 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 :: IOLike m => ResourceKey m -> m (Maybe (Context m)) unsafeRelease :: ResourceKey m -> m (Maybe (Context m)) unsafeRelease (ResourceKey ResourceRegistry m rr ResourceId rid) = do m (Maybe (Context m)) -> m (Maybe (Context m)) forall (m :: * -> *) a. MonadMask m => m a -> m a mask_ (m (Maybe (Context m)) -> m (Maybe (Context m))) -> m (Maybe (Context m)) -> m (Maybe (Context m)) forall a b. (a -> b) -> a -> b $ do Maybe (Resource m) mResource <- ResourceRegistry m -> State (RegistryState m) (Maybe (Resource m)) -> m (Maybe (Resource m)) forall (m :: * -> *) a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr (State (RegistryState m) (Maybe (Resource m)) -> m (Maybe (Resource m))) -> State (RegistryState m) (Maybe (Resource m)) -> m (Maybe (Resource m)) forall a b. (a -> b) -> a -> b $ ResourceId -> State (RegistryState m) (Maybe (Resource m)) forall (m :: * -> *). ResourceId -> State (RegistryState m) (Maybe (Resource m)) removeResource ResourceId rid case Maybe (Resource m) mResource of Maybe (Resource m) Nothing -> Maybe (Context m) -> m (Maybe (Context m)) forall (m :: * -> *) a. Monad m => a -> m a return Maybe (Context m) forall a. Maybe a Nothing Just Resource m resource -> do Bool actuallyReleased <- Resource m -> m Bool forall (m :: * -> *). Resource m -> m Bool releaseResource Resource m resource Maybe (Context m) -> m (Maybe (Context m)) forall (m :: * -> *) a. Monad m => a -> m a return (Maybe (Context m) -> m (Maybe (Context m))) -> Maybe (Context m) -> m (Maybe (Context m)) forall a b. (a -> b) -> a -> b $ if Bool actuallyReleased then Context m -> Maybe (Context m) forall a. a -> Maybe a Just (Resource m -> Context m forall (m :: * -> *). Resource m -> Context m resourceContext Resource m resource) else Maybe (Context m) forall a. Maybe a Nothing -- | Release all resources in the 'ResourceRegistry' without closing. -- -- See 'closeRegistry' for more details. releaseAll :: (IOLike m, HasCallStack) => ResourceRegistry m -> m () releaseAll :: ResourceRegistry m -> m () releaseAll ResourceRegistry m rr = do Context m context <- m (Context m) forall (m :: * -> *). (IOLike m, HasCallStack) => m (Context m) captureContext Bool -> m () -> m () forall (f :: * -> *). Applicative f => Bool -> f () -> f () unless (Context m -> ThreadId m forall (m :: * -> *). Context m -> ThreadId m contextThreadId Context m context ThreadId m -> ThreadId m -> Bool forall a. Eq a => a -> a -> Bool == Context m -> ThreadId m forall (m :: * -> *). Context m -> ThreadId m contextThreadId (ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext ResourceRegistry m rr)) (m () -> m ()) -> m () -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistryThreadException -> m () forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a throwIO (ResourceRegistryThreadException -> m ()) -> ResourceRegistryThreadException -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistryClosedFromWrongThread :: forall (m :: * -> *). IOLike m => Context m -> Context m -> ResourceRegistryThreadException ResourceRegistryClosedFromWrongThread { resourceRegistryCreatedIn :: Context m resourceRegistryCreatedIn = ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext ResourceRegistry m rr , resourceRegistryUsedIn :: Context m resourceRegistryUsedIn = Context m context } m [Context m] -> m () forall (f :: * -> *) a. Functor f => f a -> f () void (m [Context m] -> m ()) -> m [Context m] -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistry m -> Context m -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] forall (m :: * -> *). IOLike m => ResourceRegistry m -> Context m -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] releaseAllHelper ResourceRegistry m rr Context m context ResourceKey m -> m (Maybe (Context m)) forall (m :: * -> *). (IOLike m, HasCallStack) => ResourceKey m -> m (Maybe (Context m)) 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 :: (IOLike m, HasCallStack) => ResourceRegistry m -> m () unsafeReleaseAll :: ResourceRegistry m -> m () unsafeReleaseAll ResourceRegistry m rr = do Context m context <- m (Context m) forall (m :: * -> *). (IOLike m, HasCallStack) => m (Context m) captureContext m [Context m] -> m () forall (f :: * -> *) a. Functor f => f a -> f () void (m [Context m] -> m ()) -> m [Context m] -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistry m -> Context m -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] forall (m :: * -> *). IOLike m => ResourceRegistry m -> Context m -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] releaseAllHelper ResourceRegistry m rr Context m context ResourceKey m -> m (Maybe (Context m)) forall (m :: * -> *). IOLike m => ResourceKey m -> m (Maybe (Context m)) unsafeRelease -- | Internal helper used by 'releaseAll' and 'unsafeReleaseAll'. releaseAllHelper :: IOLike m => ResourceRegistry m -> Context m -> (ResourceKey m -> m (Maybe (Context m))) -- ^ How to release a resource -> m [Context m] releaseAllHelper :: ResourceRegistry m -> Context m -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] releaseAllHelper ResourceRegistry m rr Context m context ResourceKey m -> m (Maybe (Context m)) releaser = m [Context m] -> m [Context m] forall (m :: * -> *) a. MonadMask m => m a -> m a mask_ (m [Context m] -> m [Context m]) -> m [Context m] -> m [Context m] forall a b. (a -> b) -> a -> b $ do Either PrettyCallStack [ResourceId] mKeys <- ResourceRegistry m -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) -> m (Either PrettyCallStack [ResourceId]) forall (m :: * -> *) a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr (State (RegistryState m) (Either PrettyCallStack [ResourceId]) -> m (Either PrettyCallStack [ResourceId])) -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) -> m (Either PrettyCallStack [ResourceId]) forall a b. (a -> b) -> a -> b $ State (RegistryState m) [ResourceId] -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) forall (m :: * -> *) a. State (RegistryState m) a -> State (RegistryState m) (Either PrettyCallStack a) unlessClosed (State (RegistryState m) [ResourceId] -> State (RegistryState m) (Either PrettyCallStack [ResourceId])) -> State (RegistryState m) [ResourceId] -> State (RegistryState m) (Either PrettyCallStack [ResourceId]) forall a b. (a -> b) -> a -> b $ (RegistryState m -> [ResourceId]) -> State (RegistryState m) [ResourceId] forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a gets RegistryState m -> [ResourceId] forall (m :: * -> *). RegistryState m -> [ResourceId] getYoungestToOldest case Either PrettyCallStack [ResourceId] mKeys of Left PrettyCallStack closed -> ResourceRegistry m -> Context m -> PrettyCallStack -> m [Context m] forall (m :: * -> *) x. IOLike m => ResourceRegistry m -> Context m -> PrettyCallStack -> m x throwRegistryClosed ResourceRegistry m rr Context m context PrettyCallStack closed Right [ResourceId] keys -> ResourceRegistry m -> [ResourceId] -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] forall (m :: * -> *). IOLike m => ResourceRegistry m -> [ResourceId] -> (ResourceKey m -> m (Maybe (Context m))) -> m [Context m] releaseResources ResourceRegistry m rr [ResourceId] keys ResourceKey m -> m (Maybe (Context m)) releaser {------------------------------------------------------------------------------- Threads -------------------------------------------------------------------------------} -- | Thread -- -- The internals of this type are not exported. data Thread m a = IOLike m => Thread { Thread m a -> ThreadId m threadId :: !(ThreadId m) , Thread m a -> ResourceId threadResourceId :: !ResourceId , Thread m a -> Async m a threadAsync :: !(Async m a) , Thread m a -> ResourceRegistry m threadRegistry :: !(ResourceRegistry m) } deriving Context -> Thread m a -> IO (Maybe ThunkInfo) Proxy (Thread m a) -> String (Context -> Thread m a -> IO (Maybe ThunkInfo)) -> (Context -> Thread m a -> IO (Maybe ThunkInfo)) -> (Proxy (Thread m a) -> String) -> NoThunks (Thread m a) forall a. (Context -> a -> IO (Maybe ThunkInfo)) -> (Context -> a -> IO (Maybe ThunkInfo)) -> (Proxy a -> String) -> NoThunks a forall (m :: * -> *) a. Context -> Thread m a -> IO (Maybe ThunkInfo) forall (m :: * -> *) a. Proxy (Thread m a) -> String showTypeOf :: Proxy (Thread m a) -> String $cshowTypeOf :: forall (m :: * -> *) a. Proxy (Thread m a) -> String wNoThunks :: Context -> Thread m a -> IO (Maybe ThunkInfo) $cwNoThunks :: forall (m :: * -> *) a. Context -> Thread m a -> IO (Maybe ThunkInfo) noThunks :: Context -> Thread m a -> IO (Maybe ThunkInfo) $cnoThunks :: forall (m :: * -> *) a. Context -> Thread m a -> IO (Maybe ThunkInfo) NoThunks via OnlyCheckWhnfNamed "Thread" (Thread m a) -- | 'Eq' instance for 'Thread' compares 'threadId' only. instance Eq (Thread m a) where Thread{threadId :: forall (m :: * -> *) a. Thread m a -> ThreadId m threadId = ThreadId m a} == :: Thread m a -> Thread m a -> Bool == Thread{threadId :: forall (m :: * -> *) a. Thread m a -> ThreadId m threadId = ThreadId m b} = ThreadId m a ThreadId m -> ThreadId m -> Bool forall a. Eq a => a -> a -> Bool == ThreadId m 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 :: IOLike m => Thread m a -> m () cancelThread :: Thread m a -> m () cancelThread = Async m a -> m () forall (m :: * -> *) a. MonadAsync m => Async m a -> m () uninterruptibleCancel (Async m a -> m ()) -> (Thread m a -> Async m a) -> Thread m a -> m () forall b c a. (b -> c) -> (a -> b) -> a -> c . Thread m a -> Async m a forall (m :: * -> *) a. Thread m a -> Async m a 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 'ThreadKilled' exception thrown by the registry. waitThread :: IOLike m => Thread m a -> m a waitThread :: Thread m a -> m a waitThread = Async m a -> m a forall (m :: * -> *) a. MonadAsync m => Async m a -> m a wait (Async m a -> m a) -> (Thread m a -> Async m a) -> Thread m a -> m a forall b c a. (b -> c) -> (a -> b) -> a -> c . Thread m a -> Async m a forall (m :: * -> *) a. Thread m a -> Async m a threadAsync -- | Lift 'waitAny' to 'Thread' waitAnyThread :: forall m a. IOLike m => [Thread m a] -> m a waitAnyThread :: [Thread m a] -> m a waitAnyThread [Thread m a] ts = (Async m a, a) -> a forall a b. (a, b) -> b snd ((Async m a, a) -> a) -> m (Async m a, a) -> m a forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> [Async m a] -> m (Async m a, a) forall (m :: * -> *) a. MonadAsync m => [Async m a] -> m (Async m a, a) waitAny ((Thread m a -> Async m a) -> [Thread m a] -> [Async m a] forall a b. (a -> b) -> [a] -> [b] map Thread m a -> Async m a forall (m :: * -> *) a. Thread m a -> Async m a threadAsync [Thread m a] ts) -- | Fork a new thread forkThread :: forall m a. (IOLike m, HasCallStack) => ResourceRegistry m -> String -- ^ Label for the thread -> m a -> m (Thread m a) forkThread :: ResourceRegistry m -> String -> m a -> m (Thread m a) forkThread ResourceRegistry m rr String label m a body = (ResourceKey m, Thread m a) -> Thread m a forall a b. (a, b) -> b snd ((ResourceKey m, Thread m a) -> Thread m a) -> m (ResourceKey m, Thread m a) -> m (Thread m a) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> ResourceRegistry m -> (ResourceId -> m (Thread m a)) -> (Thread m a -> m ()) -> m (ResourceKey m, Thread m a) forall (m :: * -> *) a. (IOLike m, HasCallStack) => ResourceRegistry m -> (ResourceId -> m a) -> (a -> m ()) -> m (ResourceKey m, a) allocate ResourceRegistry m rr (\ResourceId key -> ResourceId -> Async m a -> Thread m a mkThread ResourceId key (Async m a -> Thread m a) -> m (Async m a) -> m (Thread m a) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> m a -> m (Async m a) forall (m :: * -> *) a. MonadAsync m => m a -> m (Async m a) async (ResourceId -> m a body' ResourceId key)) Thread m a -> m () forall (m :: * -> *) a. IOLike m => Thread m a -> m () cancelThread where mkThread :: ResourceId -> Async m a -> Thread m a mkThread :: ResourceId -> Async m a -> Thread m a mkThread ResourceId rid Async m a child = Thread :: forall (m :: * -> *) a. IOLike m => ThreadId m -> ResourceId -> Async m a -> ResourceRegistry m -> Thread m a Thread { threadId :: ThreadId m threadId = Async m a -> ThreadId m forall (m :: * -> *) a. MonadAsync m => Async m a -> ThreadId m asyncThreadId Async m a child , threadResourceId :: ResourceId threadResourceId = ResourceId rid , threadAsync :: Async m a threadAsync = Async m a child , threadRegistry :: ResourceRegistry m threadRegistry = ResourceRegistry m rr } body' :: ResourceId -> m a body' :: ResourceId -> m a body' ResourceId rid = do ThreadId m me <- m (ThreadId m) forall (m :: * -> *). MonadThread m => m (ThreadId m) myThreadId ThreadId m -> String -> m () forall (m :: * -> *). MonadThread m => ThreadId m -> String -> m () labelThread ThreadId m me String label (ThreadId m -> m () registerThread ThreadId m me m () -> m a -> m a forall (m :: * -> *) a b. Monad m => m a -> m b -> m b >> m a body) m a -> m () -> m a forall (m :: * -> *) a b. MonadThrow m => m a -> m b -> m a `finally` ThreadId m -> ResourceId -> m () unregisterThread ThreadId m me ResourceId 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 :: ThreadId m -> m () registerThread ThreadId m tid = ResourceRegistry m -> State (RegistryState m) () -> m () forall (m :: * -> *) a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr (State (RegistryState m) () -> m ()) -> State (RegistryState m) () -> m () forall a b. (a -> b) -> a -> b $ ThreadId m -> State (RegistryState m) () forall (m :: * -> *). IOLike m => ThreadId m -> State (RegistryState m) () insertThread ThreadId m 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 :: ThreadId m -> ResourceId -> m () unregisterThread ThreadId m tid ResourceId rid = ResourceRegistry m -> State (RegistryState m) () -> m () forall (m :: * -> *) a. IOLike m => ResourceRegistry m -> State (RegistryState m) a -> m a updateState ResourceRegistry m rr (State (RegistryState m) () -> m ()) -> State (RegistryState m) () -> m () forall a b. (a -> b) -> a -> b $ do ThreadId m -> State (RegistryState m) () forall (m :: * -> *). IOLike m => ThreadId m -> State (RegistryState m) () removeThread ThreadId m tid StateT (RegistryState m) Identity (Maybe (Resource m)) -> State (RegistryState m) () forall (f :: * -> *) a. Functor f => f a -> f () void (StateT (RegistryState m) Identity (Maybe (Resource m)) -> State (RegistryState m) ()) -> StateT (RegistryState m) Identity (Maybe (Resource m)) -> State (RegistryState m) () forall a b. (a -> b) -> a -> b $ ResourceId -> StateT (RegistryState m) Identity (Maybe (Resource m)) forall (m :: * -> *). ResourceId -> State (RegistryState m) (Maybe (Resource m)) removeResource ResourceId 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 'withAync' 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 :: IOLike m => ResourceRegistry m -> String -- ^ Label for the thread -> m a -> (Thread m a -> m b) -> m b withThread :: ResourceRegistry m -> String -> m a -> (Thread m a -> m b) -> m b withThread ResourceRegistry m rr String label m a body = m (Thread m a) -> (Thread m a -> m ()) -> (Thread m a -> m b) -> m b forall (m :: * -> *) a b c. MonadThrow m => m a -> (a -> m b) -> (a -> m c) -> m c bracket (ResourceRegistry m -> String -> m a -> m (Thread m a) forall (m :: * -> *) a. (IOLike m, HasCallStack) => ResourceRegistry m -> String -> m a -> m (Thread m a) forkThread ResourceRegistry m rr String label m a body) Thread m a -> m () forall (m :: * -> *) a. IOLike m => Thread m a -> m () cancelThread -- | Link specified 'Thread' to the (thread that created) the registry linkToRegistry :: IOLike m => Thread m a -> m () linkToRegistry :: Thread m a -> m () linkToRegistry Thread m a t = ThreadId m -> Async m a -> m () forall (m :: * -> *) a. (MonadAsync m, MonadFork m, MonadMask m) => ThreadId m -> Async m a -> m () linkTo (ResourceRegistry m -> ThreadId m forall (m :: * -> *). ResourceRegistry m -> ThreadId m registryThread (ResourceRegistry m -> ThreadId m) -> ResourceRegistry m -> ThreadId m forall a b. (a -> b) -> a -> b $ Thread m a -> ResourceRegistry m forall (m :: * -> *) a. Thread m a -> ResourceRegistry m threadRegistry Thread m a t) (Thread m a -> Async m a forall (m :: * -> *) a. Thread m a -> Async m a threadAsync Thread m a t) -- | Fork a thread and link to it to the registry. -- -- This function is just a convenience. forkLinkedThread :: (IOLike m, HasCallStack) => ResourceRegistry m -> String -- ^ Label for the thread -> m a -> m (Thread m a) forkLinkedThread :: ResourceRegistry m -> String -> m a -> m (Thread m a) forkLinkedThread ResourceRegistry m rr String label m a body = do Thread m a t <- ResourceRegistry m -> String -> m a -> m (Thread m a) forall (m :: * -> *) a. (IOLike m, HasCallStack) => ResourceRegistry m -> String -> m a -> m (Thread m a) forkThread ResourceRegistry m rr String label m a 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). Thread m a -> m () forall (m :: * -> *) a. IOLike m => Thread m a -> m () linkToRegistry Thread m a t Thread m a -> m (Thread m a) forall (m :: * -> *) a. Monad m => a -> m a return Thread m a t {------------------------------------------------------------------------------- Check that registry is used from known thread -------------------------------------------------------------------------------} ensureKnownThread :: forall m. IOLike m => ResourceRegistry m -> Context m -> m () ensureKnownThread :: ResourceRegistry m -> Context m -> m () ensureKnownThread ResourceRegistry m rr Context m context = do Bool isKnown <- m Bool checkIsKnown Bool -> m () -> m () forall (f :: * -> *). Applicative f => Bool -> f () -> f () unless Bool isKnown (m () -> m ()) -> m () -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistryThreadException -> m () forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a throwIO (ResourceRegistryThreadException -> m ()) -> ResourceRegistryThreadException -> m () forall a b. (a -> b) -> a -> b $ ResourceRegistryUsedFromUntrackedThread :: forall (m :: * -> *). IOLike m => Context m -> Context m -> ResourceRegistryThreadException ResourceRegistryUsedFromUntrackedThread { resourceRegistryCreatedIn :: Context m resourceRegistryCreatedIn = ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext ResourceRegistry m rr , resourceRegistryUsedIn :: Context m resourceRegistryUsedIn = Context m context } where checkIsKnown :: m Bool checkIsKnown :: m Bool checkIsKnown | Context m -> ThreadId m forall (m :: * -> *). Context m -> ThreadId m contextThreadId Context m context ThreadId m -> ThreadId m -> Bool forall a. Eq a => a -> a -> Bool == Context m -> ThreadId m forall (m :: * -> *). Context m -> ThreadId m contextThreadId (ResourceRegistry m -> Context m forall (m :: * -> *). ResourceRegistry m -> Context m registryContext ResourceRegistry m rr) = Bool -> m Bool forall (m :: * -> *) a. Monad m => a -> m a return Bool True | Bool otherwise = STM m Bool -> m Bool forall (m :: * -> *) a. (MonadSTM m, HasCallStack) => STM m a -> m a atomically (STM m Bool -> m Bool) -> STM m Bool -> m Bool forall a b. (a -> b) -> a -> b $ do KnownThreads Set (ThreadId m) ts <- RegistryState m -> KnownThreads m forall (m :: * -> *). RegistryState m -> KnownThreads m registryThreads (RegistryState m -> KnownThreads m) -> STM m (RegistryState m) -> STM m (KnownThreads m) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> StrictTVar m (RegistryState m) -> STM m (RegistryState m) forall (m :: * -> *) a. MonadSTM m => StrictTVar m a -> STM m a readTVar (ResourceRegistry m -> StrictTVar m (RegistryState m) forall (m :: * -> *). ResourceRegistry m -> StrictTVar m (RegistryState m) registryState ResourceRegistry m rr) Bool -> STM m Bool forall (m :: * -> *) a. Monad m => a -> m a return (Bool -> STM m Bool) -> Bool -> STM m Bool forall a b. (a -> b) -> a -> b $ Context m -> ThreadId m forall (m :: * -> *). Context m -> ThreadId m contextThreadId Context m context ThreadId m -> Set (ThreadId m) -> Bool forall a. Ord a => a -> Set a -> Bool `Set.member` Set (ThreadId m) 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. IOLike 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. IOLike 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 -------------------------------------------------------------------------------} data Context m = IOLike m => Context { -- | CallStack in which it was created Context m -> PrettyCallStack contextCallStack :: !PrettyCallStack -- | Thread that created the registry or resource , Context m -> ThreadId m contextThreadId :: !(ThreadId m) } -- Existential type; we can't use generics instance NoThunks (Context m) where showTypeOf :: Proxy (Context m) -> String showTypeOf Proxy (Context m) _ = String "Context" wNoThunks :: Context -> Context m -> IO (Maybe ThunkInfo) wNoThunks Context ctxt (Context PrettyCallStack cs ThreadId m tid) = [IO (Maybe ThunkInfo)] -> IO (Maybe ThunkInfo) allNoThunks [ Context -> PrettyCallStack -> IO (Maybe ThunkInfo) forall a. NoThunks a => Context -> a -> IO (Maybe ThunkInfo) noThunks Context ctxt PrettyCallStack cs , Context -> InspectHeapNamed "ThreadId" (ThreadId m) -> IO (Maybe ThunkInfo) forall a. NoThunks a => Context -> a -> IO (Maybe ThunkInfo) noThunks Context ctxt (ThreadId m -> InspectHeapNamed "ThreadId" (ThreadId m) forall (name :: Symbol) a. a -> InspectHeapNamed name a InspectHeapNamed @"ThreadId" ThreadId m tid) ] deriving instance Show (Context m) captureContext :: IOLike m => HasCallStack => m (Context m) captureContext :: m (Context m) captureContext = PrettyCallStack -> ThreadId m -> Context m forall (m :: * -> *). IOLike m => PrettyCallStack -> ThreadId m -> Context m Context PrettyCallStack HasCallStack => PrettyCallStack prettyCallStack (ThreadId m -> Context m) -> m (ThreadId m) -> m (Context m) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> m (ThreadId m) forall (m :: * -> *). MonadThread m => m (ThreadId m) myThreadId