packages feed

context (empty) → 0.1.0.0

raw patch · 14 files changed

+2113/−0 lines, 14 filesdep +asyncdep +basedep +containers

Dependencies added: async, base, containers, context, ghc-prim, hspec

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Change log++## 0.1.0.0++Initial release
+ LICENSE.md view
@@ -0,0 +1,23 @@+[The MIT License (MIT)][]++Copyright (c) 2020 Jason Shipman++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the "Software"), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies+of the Software, and to permit persons to whom the Software is furnished to do+so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.++[The MIT License (MIT)]: https://opensource.org/licenses/MIT
+ README.md view
@@ -0,0 +1,36 @@+# [context][]++[![Version badge][]][version]++🚧 This README is under construction and could use some love. 🚧++`context` provides thread-indexed storage around arbitrary context+values. The interface supports nesting context values per thread, and at+any point, the calling thread may ask for their current context.++## Synopsis++```haskell+{-# LANGUAGE BlockArguments #-}++import qualified Context++data Thing = Thing+  { stuff :: Int+  }++main :: IO ()+main = do+  Context.withEmptyStore \store -> do+    Context.use store Thing { stuff = 1 } do+      Context.use store Thing { stuff = 2 } do+        thing2 <- Context.mine store+        -- ...+      number1 <- Context.mines store stuff+```++See the Haddocks for more info on the library.++[context]: https://github.com/jship/context/context+[Version badge]: https://img.shields.io/hackage/v/context?color=brightgreen&label=version&logo=haskell+[version]: https://hackage.haskell.org/package/context
+ context.cabal view
@@ -0,0 +1,70 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: f039a0aad49f186ced980c33e32c16906b02598865a644fb1142fda2f631183d++name:           context+version:        0.1.0.0+synopsis:       Thread-indexed, nested contexts+description:    Thread-indexed storage around arbitrary context values. The interface supports+                nesting context values per thread, and at any point, the calling thread may+                ask for their current context.+category:       Data+homepage:       https://github.com/jship/context#readme+bug-reports:    https://github.com/jship/context/issues+author:         Jason Shipman+maintainer:     Jason Shipman+copyright:      2020 (c) Jason Shipman+license:        MIT+license-file:   LICENSE.md+build-type:     Simple+extra-source-files:+    CHANGELOG.md+    LICENSE.md+    package.yaml+    README.md++source-repository head+  type: git+  location: https://github.com/jship/context++library+  exposed-modules:+      Context+      Context.Concurrent+      Context.Implicit+      Context.Internal+      Context.Storage+  other-modules:+      Paths_context+  hs-source-dirs:+      library+  ghc-options: -Wall -fwarn-tabs -Wincomplete-uni-patterns -Wredundant-constraints+  build-depends:+      base >=4.12 && <5+    , containers >=0.6.0.1 && <0.7+  default-language: Haskell2010++test-suite context-test-suite+  type: exitcode-stdio-1.0+  main-is: Driver.hs+  other-modules:+      Test.Context.ConcurrentSpec+      Test.Context.ImplicitSpec+      Test.ContextSpec+      Paths_context+  hs-source-dirs:+      test-suite+  ghc-options: -Wall -fwarn-tabs -Wincomplete-uni-patterns -Wredundant-constraints -threaded+  build-tool-depends:+      hspec-discover:hspec-discover+  build-depends:+      async+    , base+    , context+    , ghc-prim+    , hspec+  default-language: Haskell2010
+ library/Context.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE StrictData #-}++module Context+  ( -- * Introduction+    -- $intro++    -- * Storage+    Store+  , withNonEmptyStore+  , withEmptyStore++    -- * Operations+    -- ** Registering context+  , use+  , adjust++    -- ** Asking for context+  , mine+  , mines++  , mineMay+  , minesMay++    -- * Exceptions+  , NotFoundException(NotFoundException, threadId)++    -- * Concurrency+  , module Context.Concurrent++    -- * Lower-level storage+  , module Context.Storage+  ) where++import Context.Concurrent+import Context.Internal (Store, mineMay, use)+import Context.Storage+import Control.Exception (Exception)+import Control.Monad ((<=<))+import GHC.Generics (Generic)+import Prelude+import qualified Context.Internal as Internal+import qualified Control.Exception as Exception++-- | An exception which may be thrown via 'mine', 'mines', and 'adjust' when the+-- calling thread does not have a registered context.+--+-- @since 0.1.0.0+data NotFoundException = NotFoundException+  { threadId :: ThreadId+  } deriving stock (Eq, Generic, Show)+    deriving anyclass Exception++-- | Provides a new, non-empty 'Store' that uses the specified context value as a+-- default when the calling thread has no registered context. 'mine', 'mines',+-- and 'adjust' are guaranteed to never throw 'NotFoundException' when applied+-- to a non-empty 'Store'.+--+-- @since 0.1.0.0+withNonEmptyStore :: ctx -> (Store ctx -> IO a) -> IO a+withNonEmptyStore = Internal.withStore defaultPropagation . Just++-- | Provides a new, empty 'Store'. 'mine', 'mines', and 'adjust' will throw+-- 'NotFoundException' when the calling thread has no registered context. Useful+-- when the 'Store' will contain context values that are always thread-specific.+--+-- @since 0.1.0.0+withEmptyStore :: (Store ctx -> IO a) -> IO a+withEmptyStore = Internal.withStore defaultPropagation Nothing++-- | Adjust the calling thread's context in the specified 'Store' for the+-- duration of the specified action. Throws a 'NotFoundException' when the+-- calling thread has no registered context.+--+-- @since 0.1.0.0+adjust :: Store ctx -> (ctx -> ctx) -> IO a -> IO a+adjust store f action = do+  adjustedContext <- mines store f+  use store adjustedContext action++-- | Provide the calling thread its current context from the specified+-- 'Store'. Throws a 'NotFoundException' when the calling thread has no+-- registered context.+--+-- @since 0.1.0.0+mine :: Store ctx -> IO ctx+mine = maybe throwContextNotFound pure <=< mineMay++-- | Provide the calling thread a selection from its current context in the+-- specified 'Store'. Throws a 'NotFoundException' when the calling+-- thread has no registered context.+--+-- @since 0.1.0.0+mines :: Store ctx -> (ctx -> a) -> IO a+mines store = maybe throwContextNotFound pure <=< minesMay store++-- | Provide the calling thread a selection from its current context in the+-- specified 'Store', if present.+--+-- @since 0.1.0.0+minesMay :: Store ctx -> (ctx -> a) -> IO (Maybe a)+minesMay store selector = fmap (fmap selector) $ mineMay store++throwContextNotFound :: IO a+throwContextNotFound = do+  threadId <- myThreadId+  Exception.throwIO $ NotFoundException { threadId }++-- $intro+--+-- This module provides an opaque 'Store' for thread-indexed storage around+-- arbitrary context values. The interface supports nesting context values per+-- thread, and at any point, the calling thread may ask for its current context.+--+-- Note that threads in Haskell have no explicit parent-child relationship. So if+-- you register a context in a 'Store' produced by 'withEmptyStore', spin up a+-- separate thread, and from that thread you ask for a context, that thread will+-- not have a context in the 'Store'. Use "Context.Concurrent" as a drop-in+-- replacement for "Control.Concurrent" to have the library handle context+-- propagation from one thread to another automatically. Otherwise, you must+-- explicitly register contexts from each thread when using a 'Store' produced by+-- 'withEmptyStore'.+--+-- If you have a default context that is always applicable to all threads, you may+-- wish to use 'withNonEmptyStore'. All threads may access this default context+-- (without leveraging "Context.Concurrent" or explicitly registering context+-- for the threads) when using a 'Store' produced by 'withNonEmptyStore'.+--+-- Regardless of how you initialize your 'Store', every thread is free to nest its+-- own specific context values.+--+-- This module is designed to be imported qualified:+--+-- > import qualified Context
+ library/Context/Concurrent.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}++module Context.Concurrent+  ( -- * Introduction+    -- $intro++    -- * Control.Concurrent wrappers+    forkIO+  , forkFinally+  , forkIOWithUnmask+  , forkOn+  , forkOnWithUnmask+  , forkOS+  , forkOSWithUnmask+  , runInBoundThread+  , runInUnboundThread++    -- * Control.Concurrent re-exports+  , ThreadId+  , myThreadId+  , killThread+  , throwTo+  , getNumCapabilities+  , setNumCapabilities+  , threadCapability+  , yield+  , threadDelay+  , threadWaitRead+  , threadWaitWrite+  , threadWaitReadSTM+  , threadWaitWriteSTM+  , rtsSupportsBoundThreads+  , isCurrentThreadBound+  , mkWeakThreadId+  , module Control.Concurrent.MVar+  , module Control.Concurrent.Chan+  , module Control.Concurrent.QSem+  , module Control.Concurrent.QSemN+  ) where++import Control.Concurrent+  ( ThreadId, getNumCapabilities, isCurrentThreadBound, killThread, mkWeakThreadId, myThreadId+  , rtsSupportsBoundThreads, setNumCapabilities, threadCapability, threadDelay, threadWaitRead+  , threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, throwTo, yield+  )+import Control.Concurrent.Chan+import Control.Concurrent.MVar+import Control.Concurrent.QSem+import Control.Concurrent.QSemN+import Control.Exception (SomeException)+import Prelude+import qualified Context.Internal as Internal+import qualified Control.Concurrent as Concurrent+import qualified Control.Exception as Exception++-- | See 'Concurrent.forkIO'.+--+-- @since 0.1.0.0+forkIO :: IO () -> IO ThreadId+forkIO action = do+  Internal.withPropagator \propagate -> do+    Concurrent.forkIO $ propagate action++-- | See 'Concurrent.forkFinally'.+--+-- @since 0.1.0.0+forkFinally :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId+forkFinally action and_then = do+  -- N.B. We re-implement forkFinally instead of delegating directly to the+  -- Control.Concurrent function. This enables us to propagate context a single+  -- time to make it available to both the thread's main action and terminating+  -- action.+  Internal.withPropagator \propagate -> do+    Exception.mask \restore -> do+      Concurrent.forkIO $ propagate do+        Exception.try (restore action) >>= and_then++-- | See 'Concurrent.forkIOWithUnmask'.+--+-- @since 0.1.0.0+forkIOWithUnmask :: ((forall a. IO a -> IO a) -> IO ()) -> IO ThreadId+forkIOWithUnmask io = do+  Internal.withPropagator \propagate -> do+    Concurrent.forkIOWithUnmask \restore -> do+      propagate $ io restore++-- | See 'Concurrent.forkOn'.+--+-- @since 0.1.0.0+forkOn :: Int -> IO () -> IO ThreadId+forkOn cpu action = do+  Internal.withPropagator \propagate -> do+    Concurrent.forkOn cpu $ propagate action++-- | See 'Concurrent.forkOnWithUnmask'.+--+-- @since 0.1.0.0+forkOnWithUnmask :: Int -> ((forall a. IO a -> IO a) -> IO ()) -> IO ThreadId+forkOnWithUnmask cpu io = do+  Internal.withPropagator \propagate -> do+    Concurrent.forkOnWithUnmask cpu \restore -> do+      propagate $ io restore++-- | See 'Concurrent.forkOS'.+--+-- @since 0.1.0.0+forkOS :: IO () -> IO ThreadId+forkOS action = do+  Internal.withPropagator \propagate -> do+    Concurrent.forkOS $ propagate action++-- | See 'Concurrent.forkOSWithUnmask'.+--+-- @since 0.1.0.0+forkOSWithUnmask :: ((forall a. IO a -> IO a) -> IO ()) -> IO ThreadId+forkOSWithUnmask io = do+  Internal.withPropagator \propagate -> do+    Concurrent.forkOSWithUnmask \restore -> do+      propagate $ io restore++-- | See 'Concurrent.runInBoundThread'.+--+-- @since 0.1.0.0+runInBoundThread :: IO a -> IO a+runInBoundThread action =+  Internal.withPropagator \propagate -> do+    Concurrent.runInBoundThread $ propagate action++-- | See 'Concurrent.runInUnboundThread'.+--+-- @since 0.1.0.0+runInUnboundThread :: IO a -> IO a+runInUnboundThread action =+  Internal.withPropagator \propagate -> do+    Concurrent.runInUnboundThread $ propagate action++-- $intro+--+-- This module provides a "Context"-compatible interface around+-- "Control.Concurrent". Depending on the 'Context.Storage.PropagationStrategy'+-- of the 'Context.Store', the @fork*@ and @run*@ functions in this module can+-- automatically propagate the calling thread's latest registered contexts, if+-- any, over so that they are also available to the thread being created.+--+-- This module is designed to be a drop-in replacement for "Control.Concurrent" so+-- that users only have to import this module instead of both this module and+-- "Control.Concurrent". It is also re-exported from "Context" for convenience.
+ library/Context/Implicit.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Context.Implicit+  ( -- * Introduction+    -- $intro++    -- * Storage+    Store+  , withNonEmptyStore+  , withEmptyStore++    -- * Operations+    -- ** Registering context+  , use+  , adjust++    -- ** Asking for context+  , mine+  , mines++  , mineMay+  , minesMay++    -- * Exceptions+  , NotFoundException(NotFoundException, threadId)++    -- * Concurrency+  , module Context.Concurrent++    -- * Lower-level storage+  , module Context.Storage+  ) where++import Context (NotFoundException(NotFoundException, threadId), Store, withEmptyStore, withNonEmptyStore)+import Context.Concurrent+import Context.Storage+import Prelude+import qualified Context++-- | Register a context in the implicit 'Store' on behalf of the calling+-- thread, for the duration of the specified action.+--+-- @since 0.1.0.0+use :: (?contextStore :: Store ctx) => ctx -> IO a -> IO a+use = Context.use ?contextStore++-- | Adjust the calling thread's context in the implicit 'Store' for the+-- duration of the specified action. Throws a 'NotFoundException' when the+-- calling thread has no registered context.+--+-- @since 0.1.0.0+adjust :: (?contextStore :: Store ctx) => (ctx -> ctx) -> IO a -> IO a+adjust = Context.adjust ?contextStore++-- | Provide the calling thread its current context from the implicit+-- 'Store'. Throws a 'NotFoundException' when the calling thread has no+-- registered context.+--+-- @since 0.1.0.0+mine :: (?contextStore :: Store ctx) => IO ctx+mine = Context.mine ?contextStore++-- | Provide the calling thread a selection from its current context in the+-- implicit 'Store'. Throws a 'NotFoundException' when the calling+-- thread has no registered context.+--+-- @since 0.1.0.0+mines :: (?contextStore :: Store ctx) => (ctx -> a) -> IO a+mines = Context.mines ?contextStore++-- | Provide the calling thread its current context from the implicit+-- 'Store', if present.+--+-- @since 0.1.0.0+mineMay :: (?contextStore :: Store ctx) => IO (Maybe ctx)+mineMay = Context.mineMay ?contextStore++-- | Provide the calling thread a selection from its current context in the+-- implicit 'Store', if present.+--+-- @since 0.1.0.0+minesMay :: (?contextStore :: Store ctx) => (ctx -> a) -> IO (Maybe a)+minesMay = Context.minesMay ?contextStore++-- $intro+--+-- This module provides the same interface provided by "Context", but uses an+-- implicit 'Store' where applicable. Usage of this module requires that the+-- implicit parameter is named @contextStore@ in calling code.+--+-- This module is designed to be imported qualified:+--+-- > import qualified Context.Implicit+--+-- If you are only working with an implicit 'Store' in your application, you may+-- prefer shortening the import name:+--+-- > import qualified Context.Implicit as Context+--+-- Usage of this module might look something like this:+--+-- > main :: IO ()+-- > main = do+-- >   let config = -- ...+-- >   withDependencies config \deps -> do+-- >     Context.withNonEmptyStore deps \depsStore -> do+-- >       let ?contextStore = depsStore+-- >       doStuff+-- >+-- > doStuff :: (?contextStore :: Store Dependencies) => IO ()+-- > doStuff = do+-- >   deps <- Context.mine+-- >   -- ...+--+-- If the application is using a 'Store' in a global variable, then this 'Store'+-- can be conveniently injected in for use with this module via a global+-- implicit parameter:+--+-- > module Dependencies where+-- >+-- > import qualified Context.Implicit as Context+-- > import GHC.Classes(IP(ip)) -- from 'ghc-prim' package+-- >+-- > data Dependencies = -- ...+-- >+-- > depsStore :: Store Dependencies+-- > depsStore = unsafePerformIO $ Context.newStore Context.defaultPropagation Nothing+-- > {-# NOINLINE depsStore #-}+-- >+-- > instance IP "contextStore" (Store Dependencies) where+-- >   ip = depsStore+--+-- With a global implicit parameter, the @(?contextStore :: Store Dependencies)@+-- constraint does not need to be threaded throughout the application's+-- signatures, but it still can be overridden within local scopes as needed.+--+-- For an intro to global implicit parameters, see this post:+-- <https://kcsongor.github.io/global-implicit-parameters/>
+ library/Context/Internal.hs view
@@ -0,0 +1,285 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE StrictData #-}++module Context.Internal+  ( -- * Disclaimer+    -- $disclaimer++    -- ** Store-related+    Store(Store, ref, key)+  , State(State, stacks, def)+  , withStore+  , newStore+  , use+  , push+  , pop+  , mineMay+  , mineMayOnDefault+  , setDefault++    -- ** Propagation-related+  , PropagationStrategy(NoPropagation, LatestPropagation)+  , Registry(Registry, ref)+  , AnyStore(MkAnyStore)+  , registry+  , emptyRegistry+  , withPropagator+  , withRegisteredPropagator+  , register+  , unregister++    -- ** Miscellaneous+  , bug+  ) where++import Control.Concurrent (ThreadId)+import Data.IORef (IORef)+import Data.Map.Strict (Map)+import Data.Unique (Unique)+import GHC.Stack (HasCallStack)+import Prelude+import System.IO.Unsafe (unsafePerformIO)+import qualified Control.Concurrent as Concurrent+import qualified Control.Exception as Exception+import qualified Data.IORef as IORef+import qualified Data.Map.Strict as Map+import qualified Data.Traversable as Traversable+import qualified Data.Unique as Unique++-- | Opaque type that manages thread-indexed storage of context values.+--+-- @since 0.1.0.0+data Store ctx = Store+  { ref :: IORef (State ctx)+  , key :: Unique+  }++data State ctx = State+  { stacks :: Map ThreadId [ctx]+  , def :: Maybe ctx+  }++-- | The 'PropagationStrategy' controls the behavior used by+-- "Context.Concurrent" when propagating context from a "parent" thread to a new+-- thread.+--+-- @since 0.1.0.0+data PropagationStrategy+  = NoPropagation+  | LatestPropagation++-- | Set the default context value for a store. If the store was initialized as+-- an empty store, this function converts it to a non-empty store. If the store+-- was initialized as a non-empty store, this overwrites the default context+-- value.+--+-- One common use case for this function is to convert an empty store in a+-- global variable to a non-empty store while the application is+-- initializing/acquiring resources:+--+-- > depsStore :: Store Dependencies+-- > depsStore = unsafePerformIO $ Context.newStore Context.defaultPropagation Nothing+-- > {-# NOINLINE depsStore #-}+-- >+-- > main :: IO ()+-- > main = do+-- >   let config = -- ...+-- >   withDependencies config \deps -> do+-- >     Context.setDefault depsStore deps+-- >     -- ...+--+-- @since 0.1.0.0+setDefault :: Store ctx -> ctx -> IO ()+setDefault Store { ref } context = do+  IORef.atomicModifyIORef' ref \state ->+    (state { def = Just context }, ())++-- | Provide the calling thread its current context from the specified+-- 'Store', if present.+--+-- @since 0.1.0.0+mineMay :: Store ctx -> IO (Maybe ctx)+mineMay = mineMayOnDefault id++mineMayOnDefault :: (Maybe ctx -> Maybe ctx) -> Store ctx -> IO (Maybe ctx)+mineMayOnDefault onDefault Store { ref } = do+  threadId <- Concurrent.myThreadId+  State { stacks, def } <- IORef.readIORef ref+  pure+    case Map.lookup threadId stacks of+      Nothing -> onDefault def+      Just [] -> bug "mineMayOnDefault"+      Just (context : _rest) -> Just context++-- | Register a context in the specified 'Store' on behalf of the calling+-- thread, for the duration of the specified action.+--+-- @since 0.1.0.0+use :: Store ctx -> ctx -> IO a -> IO a+use store context = Exception.bracket_ (push store context) (pop store)++-- | Provides a new 'Store'. This is a lower-level function and is provided+-- mainly to give library authors more fine-grained control when using a 'Store'+-- as an implementation detail.+--+-- 'Context.withNonEmptyStore'/'Context.withEmptyStore' should generally be preferred over this+-- function when acquiring a 'Store'.+--+-- @since 0.1.0.0+withStore+  :: PropagationStrategy+  -- ^ The strategy used by "Context.Concurrent" for propagating context from a+  -- "parent" thread to a new thread.+  -> Maybe ctx+  -- ^ The default value for the 'Store'.+  --+  -- Providing a value will produce a non-empty 'Store' such that 'Context.mine',+  -- 'Context.mines', and 'Context.adjust' are guaranteed to never throw 'Context.NotFoundException'+  -- when applied to this 'Store'.+  --+  -- Providing 'Nothing' will produce an empty 'Store' such that 'Context.mine',+  -- 'Context.mines', and 'Context.adjust' will throw 'Context.NotFoundException' when the calling+  -- thread has no registered context. Providing 'Nothing' is useful when the+  -- 'Store' will contain context values that are always thread-specific.+  -> (Store ctx -> IO a)+  -> IO a+withStore propagationStrategy mContext f = do+  store <- newStore propagationStrategy mContext+  Exception.finally (f store) do+    case propagationStrategy of+      NoPropagation -> pure ()+      LatestPropagation -> unregister registry store++-- | Creates a new 'Store'. This is a lower-level function and is provided+-- /only/ to support the use case of creating a 'Store' as a global:+--+-- > store :: Store Int+-- > store = unsafePerformIO $ Context.newStore Context.defaultPropagation Nothing+-- > {-# NOINLINE store #-}+--+-- Outside of the global variable use case, 'Context.withNonEmptyStore',+-- 'Context.withEmptyStore', or even the lower-level+-- 'Context.Storage.withStore' should /always/ be preferred over this function+-- when acquiring a 'Store'.+--+-- @since 0.1.0.0+newStore+  :: PropagationStrategy+  -- ^ The strategy used by "Context.Concurrent" for propagating context from a+  -- "parent" thread to a new thread.+  -> Maybe ctx+  -- ^ The default value for the 'Store'.+  --+  -- Providing a value will produce a non-empty 'Store' such that 'Context.mine',+  -- 'Context.mines', and 'Context.adjust' are guaranteed to never throw 'Context.NotFoundException'+  -- when applied to this 'Store'.+  --+  -- Providing 'Nothing' will produce an empty 'Store' such that 'Context.mine',+  -- 'Context.mines', and 'Context.adjust' will throw 'Context.NotFoundException' when the calling+  -- thread has no registered context. Providing 'Nothing' is useful when the+  -- 'Store' will contain context values that are always thread-specific.+  -> IO (Store ctx)+newStore propagationStrategy def = do+  key <- Unique.newUnique+  ref <- IORef.newIORef State { stacks = Map.empty, def }+  let store = Store { ref, key }+  case propagationStrategy of+    NoPropagation -> pure ()+    LatestPropagation -> register registry store+  pure store++push :: Store ctx -> ctx -> IO ()+push Store { ref } context = do+  threadId <- Concurrent.myThreadId+  IORef.atomicModifyIORef' ref \state@State { stacks } ->+    case Map.lookup threadId stacks of+      Nothing ->+        (state { stacks = Map.insert threadId [context] stacks }, ())+      Just contexts ->+        (state { stacks = Map.insert threadId (context : contexts) stacks}, ())++pop :: Store ctx -> IO ()+pop Store { ref } = do+  threadId <- Concurrent.myThreadId+  IORef.atomicModifyIORef' ref \state@State { stacks } ->+    case Map.lookup threadId stacks of+      Nothing -> bug "pop-1"+      Just [] -> bug "pop-2"++      Just [_context] ->+        (state { stacks = Map.delete threadId stacks }, ())+      Just (_context : rest) ->+        (state { stacks = Map.insert threadId rest stacks }, ())++data AnyStore where+  MkAnyStore :: forall ctx. Store ctx -> AnyStore++newtype Registry = Registry+  { ref :: IORef (Map Unique AnyStore)+  }++registry :: Registry+registry = unsafePerformIO emptyRegistry+{-# NOINLINE registry #-}++emptyRegistry :: IO Registry+emptyRegistry = do+  ref <- IORef.newIORef Map.empty+  pure Registry { ref }++withPropagator :: ((IO a -> IO a) -> IO b) -> IO b+withPropagator = withRegisteredPropagator registry++-- The with-style here is not necessary but it helps keep calling code honest by+-- encouraging not holding onto the propagator any longer than needed. It also+-- makes the signature compatible if the registry's state is ever changed to an+-- MVar and withMVar is used within this function. At this time, an IORef is+-- sufficient because while other stores could be registered after the calling+-- thread reads from the IORef, it would be impossible for the calling thread to+-- have any contexts in those new stores, so there would be nothing to propagate+-- from them.+withRegisteredPropagator :: Registry -> ((IO a -> IO a) -> IO b) -> IO b+withRegisteredPropagator Registry { ref } f = do+  stores <- IORef.readIORef ref+  propagator <- do+    fmap (foldr (.) id) do+      Traversable.for stores \case+        MkAnyStore store -> do+          -- N.B. When propagating context and the "parent" thread doesn't+          -- have any specific context in this particular store but there is+          -- a default, if we just used mineMay directly we would grab the+          -- default and then unnecessarily propagate it as a specific context+          -- for the new thread. Here we override the default value to Nothing+          -- as an optimization.+          mineMayOnDefault (const Nothing) store >>= \case+            Nothing -> pure id+            Just context -> pure $ use store context+  f propagator++register :: Registry -> Store ctx -> IO ()+register Registry { ref } store@Store { key } = do+  IORef.atomicModifyIORef' ref \stores ->+    (Map.insert key (MkAnyStore store) stores, ())++unregister :: Registry -> Store ctx -> IO ()+unregister Registry { ref } Store { key } = do+  IORef.atomicModifyIORef' ref \stores ->+    (Map.delete key stores, ())++bug :: HasCallStack => String -> a+bug prefix =+  error+    $ "Context." <> prefix <> ": Impossible! (if you see this message, please "+        <> "report it as a bug at https://github.com/jship/context)"++-- $disclaimer+--+-- In general, changes to this module will not be reflected in the library's+-- version updates. Direct use of this module should be done with /extreme/ care+-- as it becomes very easy to violate the library's invariants.
+ library/Context/Storage.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE StrictData #-}++module Context.Storage+  ( -- * Introduction+    -- $intro++    -- * Lower-level storage+    withStore+  , newStore+  , setDefault++    -- ** Propagation strategies+  , PropagationStrategy+  , defaultPropagation+  , noPropagation+  ) where++import Context.Internal (PropagationStrategy(LatestPropagation, NoPropagation), newStore, setDefault, withStore)++-- | The default 'PropagationStrategy'. For any 'Context.Store' initialized with this+-- 'PropagationStrategy', "Context.Concurrent" will automatically propagate the+-- "parent" thread's latest context value from this 'Context.Store' so that that context+-- is accessible in a newly-created thread.+--+-- @since 0.1.0.0+defaultPropagation :: PropagationStrategy+defaultPropagation = LatestPropagation++-- | This 'PropagationStrategy' does no propagation whatsoever. For any 'Context.Store'+-- initialized with this 'PropagationStrategy', "Context.Concurrent" will /not/+-- propagate the "parent" thread's context values from this 'Context.Store' in any way+-- to the newly-created thread.+--+-- @since 0.1.0.0+noPropagation :: PropagationStrategy+noPropagation = NoPropagation++-- $intro+--+-- This module provides lower-level functions for acquiring and working with+-- 'Context.Store' values. It may be useful for:+--+--   * library authors using a 'Context.Store' value(s) as an implementation+--     detail+--   * application developers leveraging a global 'Context.Store' value(s)+--+-- In any case, this module provides more fine-grained control over+-- 'Context.Store' creation. The most important aspect of this control is being+-- able to precisely specify how "Context.Concurrent" will behave in regards to+-- context propagation.+--+-- While this module is lower-level than the "Context" module, it is still+-- re-exported from "Context" out of convenience.
+ package.yaml view
@@ -0,0 +1,47 @@+name: context+version: '0.1.0.0'+github: "jship/context"+license: MIT+license-file: LICENSE.md+copyright: 2020 (c) Jason Shipman+author: "Jason Shipman"+maintainer: "Jason Shipman"+synopsis: Thread-indexed, nested contexts+description: |+  Thread-indexed storage around arbitrary context values. The interface supports+  nesting context values per thread, and at any point, the calling thread may+  ask for their current context.+category: Data++extra-source-files:+- CHANGELOG.md+- LICENSE.md+- package.yaml+- README.md++ghc-options:+  - -Wall+  - -fwarn-tabs+  - -Wincomplete-uni-patterns+  - -Wredundant-constraints++library:+  dependencies:+  - base >=4.12 && <5+  - containers >=0.6.0.1 && <0.7+  source-dirs: library++tests:+  context-test-suite:+    source-dirs: test-suite+    main: Driver.hs+    build-tools:+    - hspec-discover+    dependencies:+    - async+    - base+    - ghc-prim+    - hspec+    - context+    ghc-options:+    - -threaded
+ test-suite/Driver.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test-suite/Test/Context/ConcurrentSpec.hs view
@@ -0,0 +1,433 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE TypeApplications #-}++module Test.Context.ConcurrentSpec+  ( spec+  ) where++import Prelude+import Test.Hspec+import qualified Context+import qualified Control.Monad as Monad++data Thing = Thing+  { stuff :: Int+  } deriving stock (Eq, Show)++spec :: Spec+spec = do+  describe "forkIO" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            Monad.void $ Context.forkIO do+              Context.mineMay store1 `shouldReturn` Nothing+              Context.mineMay store2 `shouldReturn` Nothing+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                Monad.void $ Context.forkIO do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    Monad.void $ Context.forkIO do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                      Context.mineMay store2 `shouldReturn` Just 'b'+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            Monad.void $ Context.forkIO do+              Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+              Context.mineMay store2 `shouldReturn` Just 'a'+              Context.putMVar threadDone ()+        Context.takeMVar threadDone++  describe "forkFinally" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            let checkStores = do+                  Context.mineMay store1 `shouldReturn` Nothing+                  Context.mineMay store2 `shouldReturn` Nothing+            Monad.void $ Context.forkFinally checkStores \_eResult -> do+              checkStores+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                let checkStores = do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                      Context.mineMay store2 `shouldReturn` Just 'a'+                Monad.void $ Context.forkFinally checkStores \_eResult -> do+                  checkStores+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    let checkStores = do+                          Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                          Context.mineMay store2 `shouldReturn` Just 'b'+                    Monad.void $ Context.forkFinally checkStores \_eResult -> do+                      checkStores+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            let checkStores = do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+            Monad.void $ Context.forkFinally checkStores \_eResult -> do+              checkStores+              Context.putMVar threadDone ()+        Context.takeMVar threadDone++  describe "forkIOWithUnmask" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            Monad.void $ Context.forkIOWithUnmask \_restore -> do+              Context.mineMay store1 `shouldReturn` Nothing+              Context.mineMay store2 `shouldReturn` Nothing+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                Monad.void $ Context.forkIOWithUnmask \_restore -> do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    Monad.void $ Context.forkIOWithUnmask \_restore -> do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                      Context.mineMay store2 `shouldReturn` Just 'b'+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            Monad.void $ Context.forkIOWithUnmask \_restore -> do+              Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+              Context.mineMay store2 `shouldReturn` Just 'a'+              Context.putMVar threadDone ()+        Context.takeMVar threadDone++  describe "forkOn" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            Monad.void $ Context.forkOn 1 do+              Context.mineMay store1 `shouldReturn` Nothing+              Context.mineMay store2 `shouldReturn` Nothing+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                Monad.void $ Context.forkOn 1 do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    Monad.void $ Context.forkOn 1 do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                      Context.mineMay store2 `shouldReturn` Just 'b'+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            Monad.void $ Context.forkOn 1 do+              Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+              Context.mineMay store2 `shouldReturn` Just 'a'+              Context.putMVar threadDone ()+        Context.takeMVar threadDone++  describe "forkOnWithUnmask" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            Monad.void $ Context.forkOnWithUnmask 1 \_restore -> do+              Context.mineMay store1 `shouldReturn` Nothing+              Context.mineMay store2 `shouldReturn` Nothing+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                Monad.void $ Context.forkOnWithUnmask 1 \_restore -> do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    Monad.void $ Context.forkOnWithUnmask 1 \_restore -> do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                      Context.mineMay store2 `shouldReturn` Just 'b'+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            Monad.void $ Context.forkOnWithUnmask 1 \_restore -> do+              Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+              Context.mineMay store2 `shouldReturn` Just 'a'+              Context.putMVar threadDone ()+        Context.takeMVar threadDone++  describe "forkOS" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            Monad.void $ Context.forkOS do+              Context.mineMay store1 `shouldReturn` Nothing+              Context.mineMay store2 `shouldReturn` Nothing+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                Monad.void $ Context.forkOS do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    Monad.void $ Context.forkOS do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                      Context.mineMay store2 `shouldReturn` Just 'b'+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            Monad.void $ Context.forkOS do+              Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+              Context.mineMay store2 `shouldReturn` Just 'a'+              Context.putMVar threadDone ()+        Context.takeMVar threadDone++  describe "forkOSWithUnmask" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            Monad.void $ Context.forkOSWithUnmask \_restore -> do+              Context.mineMay store1 `shouldReturn` Nothing+              Context.mineMay store2 `shouldReturn` Nothing+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                Monad.void $ Context.forkOSWithUnmask \_restore -> do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    Monad.void $ Context.forkOSWithUnmask \_restore -> do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                      Context.mineMay store2 `shouldReturn` Just 'b'+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            Monad.void $ Context.forkOSWithUnmask \_restore -> do+              Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+              Context.mineMay store2 `shouldReturn` Just 'a'+              Context.putMVar threadDone ()+        Context.takeMVar threadDone++  describe "runInBoundThread" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            Context.runInBoundThread do+              Context.mineMay store1 `shouldReturn` Nothing+              Context.mineMay store2 `shouldReturn` Nothing+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                Context.runInBoundThread do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    Context.runInBoundThread do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                      Context.mineMay store2 `shouldReturn` Just 'b'+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            Context.runInBoundThread do+              Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+              Context.mineMay store2 `shouldReturn` Just 'a'+              Context.putMVar threadDone ()+        Context.takeMVar threadDone++  describe "runInUnboundThread" do+    describe "mineMay" do+      it "empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore @Thing \store1 -> do+          Context.withEmptyStore @Char \store2 -> do+            Context.runInUnboundThread do+              Context.mineMay store1 `shouldReturn` Nothing+              Context.mineMay store2 `shouldReturn` Nothing+              Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with registered context" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.withEmptyStore \store2 -> do+              Context.use store2 'a' do+                Context.runInUnboundThread do+                  Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+                  Context.mineMay store2 `shouldReturn` Just 'a'+                  Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "initially-empty stores with nested contexts" do+        threadDone <- Context.newEmptyMVar+        Context.withEmptyStore \store1 -> do+          Context.use store1 Thing { stuff = 1 } do+            Context.use store1 Thing { stuff = 2 } do+              Context.withEmptyStore \store2 -> do+                Context.use store2 'a' do+                  Context.use store2 'b' do+                    Context.runInUnboundThread do+                      Context.mineMay store1 `shouldReturn` Just Thing { stuff = 2 }+                      Context.mineMay store2 `shouldReturn` Just 'b'+                      Context.putMVar threadDone ()+        Context.takeMVar threadDone+      it "non-empty stores" do+        threadDone <- Context.newEmptyMVar+        Context.withNonEmptyStore Thing { stuff = 1 } \store1 -> do+          Context.withNonEmptyStore 'a' \store2 -> do+            Context.runInUnboundThread do+              Context.mineMay store1 `shouldReturn` Just Thing { stuff = 1 }+              Context.mineMay store2 `shouldReturn` Just 'a'+              Context.putMVar threadDone ()+        Context.takeMVar threadDone
+ test-suite/Test/Context/ImplicitSpec.hs view
@@ -0,0 +1,248 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}++{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE TypeApplications #-}++module Test.Context.ImplicitSpec+  ( spec+  ) where++import Context.Implicit (Store)+import Control.Concurrent (ThreadId)+import GHC.Classes (IP(ip))+import Prelude+import System.IO.Unsafe (unsafePerformIO)+import Test.Hspec+import qualified Context.Implicit as Context+import qualified Control.Concurrent as Concurrent+import qualified Control.Concurrent.Async as Async++data Thing = Thing+  { stuff :: Int+  } deriving stock (Eq, Show)++store :: Store Thing+store = unsafePerformIO $ Context.newStore Context.defaultPropagation Nothing+{-# NOINLINE store #-}++instance IP "contextStore" (Store Thing) where+  ip = store++spec :: Spec+spec = do+  describe "emptyStore" do+    describe "mineMay" do+      it "empty" do+        Context.mineMay `shouldReturn` Nothing+      it "single context" do+        Context.use Thing { stuff = 1 } do+          Context.mineMay `shouldReturn` Just Thing { stuff = 1 }+        Context.mineMay `shouldReturn` Nothing+      it "nested contexts" do+        Context.use Thing { stuff = 1 } do+          Context.mineMay `shouldReturn` Just Thing { stuff = 1 }+          Context.use Thing { stuff = 2 } do+            Context.mineMay `shouldReturn` Just Thing { stuff = 2 }+            Context.use Thing { stuff = 3 } do+              Context.mineMay `shouldReturn` Just Thing { stuff = 3 }+            Context.mineMay `shouldReturn` Just Thing { stuff = 2 }+          Context.use Thing { stuff = 4 } do+            Context.mineMay `shouldReturn` Just Thing { stuff = 4 }+          Context.mineMay `shouldReturn` Just Thing { stuff = 1 }+        Context.mineMay `shouldReturn` Nothing+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Async.forConcurrently_ [1 :: Int ..10] $ const do+          Context.mineMay `shouldReturn` Nothing+          Context.use (mkContext 1) do+            Context.mineMay `shouldReturn` Just (mkContext 1)+            Context.use (mkContext 2) do+              Context.mineMay `shouldReturn` Just (mkContext 2)+              Context.use (mkContext 3) do+                Context.mineMay `shouldReturn` Just (mkContext 3)+              Context.mineMay `shouldReturn` Just (mkContext 2)+            Context.use (mkContext 4) do+              Context.mineMay `shouldReturn` Just (mkContext 4)+            Context.mineMay `shouldReturn` Just (mkContext 1)+          Context.mineMay `shouldReturn` Nothing+        Context.mineMay `shouldReturn` Nothing++    describe "minesMay" do+      it "empty" do+        Context.minesMay stuff `shouldReturn` Nothing+      it "single context" do+        Context.use Thing { stuff = 1 } do+          Context.minesMay stuff `shouldReturn` Just 1+        Context.minesMay stuff `shouldReturn` Nothing+      it "nested contexts" do+        Context.use Thing { stuff = 1 } do+          Context.minesMay stuff `shouldReturn` Just 1+          Context.use Thing { stuff = 2 } do+            Context.minesMay stuff `shouldReturn` Just 2+            Context.use Thing { stuff = 3 } do+              Context.minesMay stuff `shouldReturn` Just 3+            Context.minesMay stuff `shouldReturn` Just 2+          Context.use Thing { stuff = 4 } do+            Context.minesMay stuff `shouldReturn` Just 4+          Context.minesMay stuff `shouldReturn` Just 1+        Context.minesMay stuff `shouldReturn` Nothing+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Async.forConcurrently_ [1 :: Int ..10] $ const do+          Context.minesMay stuff `shouldReturn` Nothing+          Context.use (mkContext 1) do+            Context.minesMay stuff `shouldReturn` Just 1+            Context.use (mkContext 2) do+              Context.minesMay stuff `shouldReturn` Just 2+              Context.use (mkContext 3) do+                Context.minesMay stuff `shouldReturn` Just 3+              Context.minesMay stuff `shouldReturn` Just 2+            Context.use (mkContext 4) do+              Context.minesMay stuff `shouldReturn` Just 4+            Context.minesMay stuff `shouldReturn` Just 1+          Context.minesMay stuff `shouldReturn` Nothing+        Context.minesMay stuff `shouldReturn` Nothing++    describe "mine" do+      it "empty" do+        threadId <- Concurrent.myThreadId+        Context.mine `shouldThrow` notFound threadId+      it "single context" do+        threadId <- Concurrent.myThreadId+        Context.use Thing { stuff = 1 } do+          Context.mine `shouldReturn` Thing { stuff = 1 }+        Context.mine `shouldThrow` notFound threadId+      it "nested contexts" do+        threadId <- Concurrent.myThreadId+        Context.use Thing { stuff = 1 } do+          Context.mine `shouldReturn` Thing { stuff = 1 }+          Context.use Thing { stuff = 2 } do+            Context.mine `shouldReturn` Thing { stuff = 2 }+            Context.use Thing { stuff = 3 } do+              Context.mine `shouldReturn` Thing { stuff = 3 }+            Context.mine `shouldReturn` Thing { stuff = 2 }+          Context.use Thing { stuff = 4 } do+            Context.mine `shouldReturn` Thing { stuff = 4 }+          Context.mine `shouldReturn` Thing { stuff = 1 }+        Context.mine `shouldThrow` notFound threadId+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        initThreadId <- Concurrent.myThreadId+        Async.forConcurrently_ [1 :: Int ..10] $ const do+          threadId <- Concurrent.myThreadId+          Context.mine `shouldThrow` notFound threadId+          Context.use (mkContext 1) do+            Context.mine `shouldReturn` mkContext 1+            Context.use (mkContext 2) do+              Context.mine `shouldReturn` mkContext 2+              Context.use (mkContext 3) do+                Context.mine `shouldReturn` mkContext 3+              Context.mine `shouldReturn` mkContext 2+            Context.use (mkContext 4) do+              Context.mine `shouldReturn` mkContext 4+            Context.mine `shouldReturn` mkContext 1+          Context.mine `shouldThrow` notFound threadId+        Context.mine `shouldThrow` notFound initThreadId++    describe "mines" do+      it "empty" do+        threadId <- Concurrent.myThreadId+        Context.mines stuff `shouldThrow` notFound threadId+      it "single context" do+        threadId <- Concurrent.myThreadId+        Context.use Thing { stuff = 1 } do+          Context.mines stuff `shouldReturn` 1+        Context.mines stuff `shouldThrow` notFound threadId+      it "nested contexts" do+        threadId <- Concurrent.myThreadId+        Context.use Thing { stuff = 1 } do+          Context.mines stuff `shouldReturn` 1+          Context.use Thing { stuff = 2 } do+            Context.mines stuff `shouldReturn` 2+            Context.use Thing { stuff = 3 } do+              Context.mines stuff `shouldReturn` 3+            Context.mines stuff `shouldReturn` 2+          Context.use Thing { stuff = 4 } do+            Context.mines stuff `shouldReturn` 4+          Context.mines stuff `shouldReturn` 1+        Context.mines stuff `shouldThrow` notFound threadId+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        initThreadId <- Concurrent.myThreadId+        Async.forConcurrently_ [1 :: Int ..10] $ const do+          threadId <- Concurrent.myThreadId+          Context.mine `shouldThrow` notFound threadId+          Context.use (mkContext 1) do+            Context.mines stuff `shouldReturn` 1+            Context.use (mkContext 2) do+              Context.mines stuff `shouldReturn` 2+              Context.use (mkContext 3) do+                Context.mines stuff `shouldReturn` 3+              Context.mines stuff `shouldReturn` 2+            Context.use (mkContext 4) do+              Context.mines stuff `shouldReturn` 4+            Context.mines stuff `shouldReturn` 1+          Context.mines stuff `shouldThrow` notFound threadId+        Context.mines stuff `shouldThrow` notFound initThreadId++    describe "adjust" do+      it "empty" do+        threadId <- Concurrent.myThreadId+        Context.adjust modifier (error "does not get here")+          `shouldThrow` notFound threadId+      it "single context" do+        threadId <- Concurrent.myThreadId+        Context.use Thing { stuff = 1 } do+          Context.mine `shouldReturn` Thing { stuff = 1 }+          Context.adjust modifier do+            Context.mine `shouldReturn` Thing { stuff = 2 }+          Context.mine `shouldReturn` Thing { stuff = 1 }+        Context.mine `shouldThrow` notFound threadId+      it "nested contexts" do+        threadId <- Concurrent.myThreadId+        Context.use Thing { stuff = 1 } do+          Context.adjust modifier do+            Context.mine `shouldReturn` Thing { stuff = 2 }+            Context.use Thing { stuff = 3 } do+              Context.mine `shouldReturn` Thing { stuff = 3 }+              Context.use Thing { stuff = 4 } do+                Context.mine `shouldReturn` Thing { stuff = 4 }+              Context.mine `shouldReturn` Thing { stuff = 3 }+            Context.use Thing { stuff = 4 } do+              Context.mine `shouldReturn` Thing { stuff = 4 }+            Context.mine `shouldReturn` Thing { stuff = 2 }+          Context.mine `shouldReturn` Thing { stuff = 1 }+        Context.mine `shouldThrow` notFound threadId+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        initThreadId <- Concurrent.myThreadId+        Async.forConcurrently_ [1 :: Int ..10] $ const do+          threadId <- Concurrent.myThreadId+          Context.mine `shouldThrow` notFound threadId+          Context.use (mkContext 1) do+            Context.adjust modifier do+              Context.mine `shouldReturn` mkContext 2+              Context.use (mkContext 3) do+                Context.mine `shouldReturn` mkContext 3+                Context.use (mkContext 4) do+                  Context.mine `shouldReturn` mkContext 4+                Context.mine `shouldReturn` mkContext 3+              Context.use (mkContext 5) do+                Context.mine `shouldReturn` mkContext 5+              Context.mine `shouldReturn` mkContext 2+            Context.mine `shouldReturn` mkContext 1+          Context.mine `shouldThrow` notFound threadId+        Context.mine `shouldThrow` notFound initThreadId++notFound :: ThreadId -> Context.NotFoundException -> Bool+notFound threadId notFoundEx =+  Context.NotFoundException { Context.threadId } == notFoundEx++modifier :: Thing -> Thing+modifier thing = thing { stuff = 2 * stuff thing }
+ test-suite/Test/ContextSpec.hs view
@@ -0,0 +1,479 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE TypeApplications #-}++module Test.ContextSpec+  ( spec+  ) where++import Control.Concurrent (ThreadId)+import Prelude+import Test.Hspec+import qualified Context+import qualified Control.Concurrent as Concurrent+import qualified Control.Concurrent.Async as Async++data Thing = Thing+  { stuff :: Int+  } deriving stock (Eq, Show)++spec :: Spec+spec = do+  describe "withEmptyStore" do+    describe "mineMay" do+      it "empty" do+        Context.withEmptyStore @Thing \store -> do+          Context.mineMay store `shouldReturn` Nothing+      it "single context" do+        Context.withEmptyStore @Thing \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mineMay store `shouldReturn` Just Thing { stuff = 1 }+          Context.mineMay store `shouldReturn` Nothing+      it "nested contexts" do+        Context.withEmptyStore @Thing \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mineMay store `shouldReturn` Just Thing { stuff = 1 }+            Context.use store Thing { stuff = 2 } do+              Context.mineMay store `shouldReturn` Just Thing { stuff = 2 }+              Context.use store Thing { stuff = 3 } do+                Context.mineMay store `shouldReturn` Just Thing { stuff = 3 }+              Context.mineMay store `shouldReturn` Just Thing { stuff = 2 }+            Context.use store Thing { stuff = 4 } do+              Context.mineMay store `shouldReturn` Just Thing { stuff = 4 }+            Context.mineMay store `shouldReturn` Just Thing { stuff = 1 }+          Context.mineMay store `shouldReturn` Nothing+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Context.withEmptyStore @Thing \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            Context.mineMay store `shouldReturn` Nothing+            Context.use store (mkContext 1) do+              Context.mineMay store `shouldReturn` Just (mkContext 1)+              Context.use store (mkContext 2) do+                Context.mineMay store `shouldReturn` Just (mkContext 2)+                Context.use store (mkContext 3) do+                  Context.mineMay store `shouldReturn` Just (mkContext 3)+                Context.mineMay store `shouldReturn` Just (mkContext 2)+              Context.use store (mkContext 4) do+                Context.mineMay store `shouldReturn` Just (mkContext 4)+              Context.mineMay store `shouldReturn` Just (mkContext 1)+            Context.mineMay store `shouldReturn` Nothing+          Context.mineMay store `shouldReturn` Nothing++    describe "minesMay" do+      it "empty" do+        Context.withEmptyStore @Thing \store -> do+          Context.minesMay store stuff `shouldReturn` Nothing+      it "single context" do+        Context.withEmptyStore @Thing \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.minesMay store stuff `shouldReturn` Just 1+          Context.minesMay store stuff `shouldReturn` Nothing+      it "nested contexts" do+        Context.withEmptyStore @Thing \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.minesMay store stuff `shouldReturn` Just 1+            Context.use store Thing { stuff = 2 } do+              Context.minesMay store stuff `shouldReturn` Just 2+              Context.use store Thing { stuff = 3 } do+                Context.minesMay store stuff `shouldReturn` Just 3+              Context.minesMay store stuff `shouldReturn` Just 2+            Context.use store Thing { stuff = 4 } do+              Context.minesMay store stuff `shouldReturn` Just 4+            Context.minesMay store stuff `shouldReturn` Just 1+          Context.minesMay store stuff `shouldReturn` Nothing+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Context.withEmptyStore @Thing \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            Context.minesMay store stuff `shouldReturn` Nothing+            Context.use store (mkContext 1) do+              Context.minesMay store stuff `shouldReturn` Just 1+              Context.use store (mkContext 2) do+                Context.minesMay store stuff `shouldReturn` Just 2+                Context.use store (mkContext 3) do+                  Context.minesMay store stuff `shouldReturn` Just 3+                Context.minesMay store stuff `shouldReturn` Just 2+              Context.use store (mkContext 4) do+                Context.minesMay store stuff `shouldReturn` Just 4+              Context.minesMay store stuff `shouldReturn` Just 1+            Context.minesMay store stuff `shouldReturn` Nothing+          Context.minesMay store stuff `shouldReturn` Nothing++    describe "mine" do+      it "empty" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.mine store `shouldThrow` notFound threadId+      it "single context" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.use store Thing { stuff = 1 } do+            Context.mine store `shouldReturn` Thing { stuff = 1 }+          Context.mine store `shouldThrow` notFound threadId+      it "nested contexts" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.use store Thing { stuff = 1 } do+            Context.mine store `shouldReturn` Thing { stuff = 1 }+            Context.use store Thing { stuff = 2 } do+              Context.mine store `shouldReturn` Thing { stuff = 2 }+              Context.use store Thing { stuff = 3 } do+                Context.mine store `shouldReturn` Thing { stuff = 3 }+              Context.mine store `shouldReturn` Thing { stuff = 2 }+            Context.use store Thing { stuff = 4 } do+              Context.mine store `shouldReturn` Thing { stuff = 4 }+            Context.mine store `shouldReturn` Thing { stuff = 1 }+          Context.mine store `shouldThrow` notFound threadId+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        initThreadId <- Concurrent.myThreadId+        Context.withEmptyStore @Thing \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            threadId <- Concurrent.myThreadId+            Context.mine store `shouldThrow` notFound threadId+            Context.use store (mkContext 1) do+              Context.mine store `shouldReturn` mkContext 1+              Context.use store (mkContext 2) do+                Context.mine store `shouldReturn` mkContext 2+                Context.use store (mkContext 3) do+                  Context.mine store `shouldReturn` mkContext 3+                Context.mine store `shouldReturn` mkContext 2+              Context.use store (mkContext 4) do+                Context.mine store `shouldReturn` mkContext 4+              Context.mine store `shouldReturn` mkContext 1+            Context.mine store `shouldThrow` notFound threadId+          Context.mine store `shouldThrow` notFound initThreadId++    describe "mines" do+      it "empty" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.mines store stuff `shouldThrow` notFound threadId+      it "single context" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.use store Thing { stuff = 1 } do+            Context.mines store stuff `shouldReturn` 1+          Context.mines store stuff `shouldThrow` notFound threadId+      it "nested contexts" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.use store Thing { stuff = 1 } do+            Context.mines store stuff `shouldReturn` 1+            Context.use store Thing { stuff = 2 } do+              Context.mines store stuff `shouldReturn` 2+              Context.use store Thing { stuff = 3 } do+                Context.mines store stuff `shouldReturn` 3+              Context.mines store stuff `shouldReturn` 2+            Context.use store Thing { stuff = 4 } do+              Context.mines store stuff `shouldReturn` 4+            Context.mines store stuff `shouldReturn` 1+          Context.mines store stuff `shouldThrow` notFound threadId+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        initThreadId <- Concurrent.myThreadId+        Context.withEmptyStore @Thing \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            threadId <- Concurrent.myThreadId+            Context.mine store `shouldThrow` notFound threadId+            Context.use store (mkContext 1) do+              Context.mines store stuff `shouldReturn` 1+              Context.use store (mkContext 2) do+                Context.mines store stuff `shouldReturn` 2+                Context.use store (mkContext 3) do+                  Context.mines store stuff `shouldReturn` 3+                Context.mines store stuff `shouldReturn` 2+              Context.use store (mkContext 4) do+                Context.mines store stuff `shouldReturn` 4+              Context.mines store stuff `shouldReturn` 1+            Context.mines store stuff `shouldThrow` notFound threadId+          Context.mines store stuff `shouldThrow` notFound initThreadId++    describe "adjust" do+      it "empty" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.adjust store modifier (error "does not get here")+            `shouldThrow` notFound threadId+      it "single context" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.use store Thing { stuff = 1 } do+            Context.mine store `shouldReturn` Thing { stuff = 1 }+            Context.adjust store modifier do+              Context.mine store `shouldReturn` Thing { stuff = 2 }+            Context.mine store `shouldReturn` Thing { stuff = 1 }+          Context.mine store `shouldThrow` notFound threadId+      it "nested contexts" do+        Context.withEmptyStore @Thing \store -> do+          threadId <- Concurrent.myThreadId+          Context.use store Thing { stuff = 1 } do+            Context.adjust store modifier do+              Context.mine store `shouldReturn` Thing { stuff = 2 }+              Context.use store Thing { stuff = 3 } do+                Context.mine store `shouldReturn` Thing { stuff = 3 }+                Context.use store Thing { stuff = 4 } do+                  Context.mine store `shouldReturn` Thing { stuff = 4 }+                Context.mine store `shouldReturn` Thing { stuff = 3 }+              Context.use store Thing { stuff = 4 } do+                Context.mine store `shouldReturn` Thing { stuff = 4 }+              Context.mine store `shouldReturn` Thing { stuff = 2 }+            Context.mine store `shouldReturn` Thing { stuff = 1 }+          Context.mine store `shouldThrow` notFound threadId+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        initThreadId <- Concurrent.myThreadId+        Context.withEmptyStore @Thing \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            threadId <- Concurrent.myThreadId+            Context.mine store `shouldThrow` notFound threadId+            Context.use store (mkContext 1) do+              Context.adjust store modifier do+                Context.mine store `shouldReturn` mkContext 2+                Context.use store (mkContext 3) do+                  Context.mine store `shouldReturn` mkContext 3+                  Context.use store (mkContext 4) do+                    Context.mine store `shouldReturn` mkContext 4+                  Context.mine store `shouldReturn` mkContext 3+                Context.use store (mkContext 5) do+                  Context.mine store `shouldReturn` mkContext 5+                Context.mine store `shouldReturn` mkContext 2+              Context.mine store `shouldReturn` mkContext 1+            Context.mine store `shouldThrow` notFound threadId+          Context.mine store `shouldThrow` notFound initThreadId++    describe "setDefault" do+      it "setting default converts store to non-empty" do+        Context.withEmptyStore @Thing \store -> do+          Context.mineMay store `shouldReturn` Nothing+          Context.setDefault store Thing { stuff = 1 }+          Context.mineMay store `shouldReturn` Just Thing { stuff = 1 }+          Context.setDefault store Thing { stuff = 2 }+          Context.mineMay store `shouldReturn` Just Thing { stuff = 2 }++  describe "withNonEmptyStore" do+    describe "mineMay" do+      it "default" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.mineMay store `shouldReturn` Just Thing { stuff = 0 }+      it "single context" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mineMay store `shouldReturn` Just Thing { stuff = 1 }+          Context.mineMay store `shouldReturn` Just Thing { stuff = 0 }+      it "nested contexts" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mineMay store `shouldReturn` Just Thing { stuff = 1 }+            Context.use store Thing { stuff = 2 } do+              Context.mineMay store `shouldReturn` Just Thing { stuff = 2 }+              Context.use store Thing { stuff = 3 } do+                Context.mineMay store `shouldReturn` Just Thing { stuff = 3 }+              Context.mineMay store `shouldReturn` Just Thing { stuff = 2 }+            Context.use store Thing { stuff = 4 } do+              Context.mineMay store `shouldReturn` Just Thing { stuff = 4 }+            Context.mineMay store `shouldReturn` Just Thing { stuff = 1 }+          Context.mineMay store `shouldReturn` Just Thing { stuff = 0 }+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Context.withNonEmptyStore (mkContext 0) \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            Context.mineMay store `shouldReturn` Just (mkContext 0)+            Context.use store (mkContext 1) do+              Context.mineMay store `shouldReturn` Just (mkContext 1)+              Context.use store (mkContext 2) do+                Context.mineMay store `shouldReturn` Just (mkContext 2)+                Context.use store (mkContext 3) do+                  Context.mineMay store `shouldReturn` Just (mkContext 3)+                Context.mineMay store `shouldReturn` Just (mkContext 2)+              Context.use store (mkContext 4) do+                Context.mineMay store `shouldReturn` Just (mkContext 4)+              Context.mineMay store `shouldReturn` Just (mkContext 1)+            Context.mineMay store `shouldReturn` Just (mkContext 0)+          Context.mineMay store `shouldReturn` Just (mkContext 0)++    describe "minesMay" do+      it "default" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.minesMay store stuff `shouldReturn` Just 0+      it "single context" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.minesMay store stuff `shouldReturn` Just 1+          Context.minesMay store stuff `shouldReturn` Just 0+      it "nested contexts" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.minesMay store stuff `shouldReturn` Just 1+            Context.use store Thing { stuff = 2 } do+              Context.minesMay store stuff `shouldReturn` Just 2+              Context.use store Thing { stuff = 3 } do+                Context.minesMay store stuff `shouldReturn` Just 3+              Context.minesMay store stuff `shouldReturn` Just 2+            Context.use store Thing { stuff = 4 } do+              Context.minesMay store stuff `shouldReturn` Just 4+            Context.minesMay store stuff `shouldReturn` Just 1+          Context.minesMay store stuff `shouldReturn` Just 0+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Context.withNonEmptyStore (mkContext 0) \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            Context.minesMay store stuff `shouldReturn` Just 0+            Context.use store (mkContext 1) do+              Context.minesMay store stuff `shouldReturn` Just 1+              Context.use store (mkContext 2) do+                Context.minesMay store stuff `shouldReturn` Just 2+                Context.use store (mkContext 3) do+                  Context.minesMay store stuff `shouldReturn` Just 3+                Context.minesMay store stuff `shouldReturn` Just 2+              Context.use store (mkContext 4) do+                Context.minesMay store stuff `shouldReturn` Just 4+              Context.minesMay store stuff `shouldReturn` Just 1+            Context.minesMay store stuff `shouldReturn` Just 0+          Context.minesMay store stuff `shouldReturn` Just 0++    describe "mine" do+      it "default" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.mine store `shouldReturn` Thing { stuff = 0 }+      it "single context" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mine store `shouldReturn` Thing { stuff = 1 }+          Context.mine store `shouldReturn` Thing { stuff = 0 }+      it "nested contexts" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mine store `shouldReturn` Thing { stuff = 1 }+            Context.use store Thing { stuff = 2 } do+              Context.mine store `shouldReturn` Thing { stuff = 2 }+              Context.use store Thing { stuff = 3 } do+                Context.mine store `shouldReturn` Thing { stuff = 3 }+              Context.mine store `shouldReturn` Thing { stuff = 2 }+            Context.use store Thing { stuff = 4 } do+              Context.mine store `shouldReturn` Thing { stuff = 4 }+            Context.mine store `shouldReturn` Thing { stuff = 1 }+          Context.mine store `shouldReturn` Thing { stuff = 0 }+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Context.withNonEmptyStore (mkContext 0) \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            Context.mine store `shouldReturn` mkContext 0+            Context.use store (mkContext 1) do+              Context.mine store `shouldReturn` mkContext 1+              Context.use store (mkContext 2) do+                Context.mine store `shouldReturn` mkContext 2+                Context.use store (mkContext 3) do+                  Context.mine store `shouldReturn` mkContext 3+                Context.mine store `shouldReturn` mkContext 2+              Context.use store (mkContext 4) do+                Context.mine store `shouldReturn` mkContext 4+              Context.mine store `shouldReturn` mkContext 1+            Context.mine store `shouldReturn` mkContext 0+          Context.mine store `shouldReturn` mkContext 0++    describe "mines" do+      it "default" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.mines store stuff `shouldReturn` 0+      it "single context" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mines store stuff `shouldReturn` 1+          Context.mines store stuff `shouldReturn` 0+      it "nested contexts" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mines store stuff `shouldReturn` 1+            Context.use store Thing { stuff = 2 } do+              Context.mines store stuff `shouldReturn` 2+              Context.use store Thing { stuff = 3 } do+                Context.mines store stuff `shouldReturn` 3+              Context.mines store stuff `shouldReturn` 2+            Context.use store Thing { stuff = 4 } do+              Context.mines store stuff `shouldReturn` 4+            Context.mines store stuff `shouldReturn` 1+          Context.mines store stuff `shouldReturn` 0+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Context.withNonEmptyStore (mkContext 0) \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            Context.mines store stuff `shouldReturn` 0+            Context.use store (mkContext 1) do+              Context.mines store stuff `shouldReturn` 1+              Context.use store (mkContext 2) do+                Context.mines store stuff `shouldReturn` 2+                Context.use store (mkContext 3) do+                  Context.mines store stuff `shouldReturn` 3+                Context.mines store stuff `shouldReturn` 2+              Context.use store (mkContext 4) do+                Context.mines store stuff `shouldReturn` 4+              Context.mines store stuff `shouldReturn` 1+            Context.mines store stuff `shouldReturn` 0+          Context.mines store stuff `shouldReturn` 0++    describe "adjust" do+      it "default" do+        Context.withNonEmptyStore Thing { stuff = 1 } \store -> do+          Context.adjust store modifier do+            Context.mine store `shouldReturn` Thing { stuff = 2 }+          Context.mine store `shouldReturn` Thing { stuff = 1 }+      it "single context" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.mine store `shouldReturn` Thing { stuff = 1 }+            Context.adjust store modifier do+              Context.mine store `shouldReturn` Thing { stuff = 2 }+            Context.mine store `shouldReturn` Thing { stuff = 1 }+          Context.mine store `shouldReturn` Thing { stuff = 0 }+      it "nested contexts" do+        Context.withNonEmptyStore Thing { stuff = 0 } \store -> do+          Context.use store Thing { stuff = 1 } do+            Context.adjust store modifier do+              Context.mine store `shouldReturn` Thing { stuff = 2 }+              Context.use store Thing { stuff = 3 } do+                Context.mine store `shouldReturn` Thing { stuff = 3 }+                Context.use store Thing { stuff = 4 } do+                  Context.mine store `shouldReturn` Thing { stuff = 4 }+                Context.mine store `shouldReturn` Thing { stuff = 3 }+              Context.use store Thing { stuff = 4 } do+                Context.mine store `shouldReturn` Thing { stuff = 4 }+              Context.mine store `shouldReturn` Thing { stuff = 2 }+            Context.mine store `shouldReturn` Thing { stuff = 1 }+          Context.mine store `shouldReturn` Thing { stuff = 0 }+      it "concurrent nested contexts" do+        let mkContext i = Thing { stuff = i }+        Context.withNonEmptyStore (mkContext 0) \store -> do+          Async.forConcurrently_ [1 :: Int ..10] $ const do+            Context.mine store `shouldReturn` mkContext 0+            Context.use store (mkContext 1) do+              Context.adjust store modifier do+                Context.mine store `shouldReturn` mkContext 2+                Context.use store (mkContext 3) do+                  Context.mine store `shouldReturn` mkContext 3+                  Context.use store (mkContext 4) do+                    Context.mine store `shouldReturn` mkContext 4+                  Context.mine store `shouldReturn` mkContext 3+                Context.use store (mkContext 5) do+                  Context.mine store `shouldReturn` mkContext 5+                Context.mine store `shouldReturn` mkContext 2+              Context.mine store `shouldReturn` mkContext 1+            Context.mine store `shouldReturn` mkContext 0+          Context.mine store `shouldReturn` mkContext 0++    describe "setDefault" do+      it "setting default overrides initial default" do+        Context.withNonEmptyStore Thing { stuff = 1 } \store -> do+          Context.mineMay store `shouldReturn` Just Thing { stuff = 1 }+          Context.setDefault store Thing { stuff = 2 }+          Context.mineMay store `shouldReturn` Just Thing { stuff = 2 }++notFound :: ThreadId -> Context.NotFoundException -> Bool+notFound threadId notFoundEx =+  Context.NotFoundException { Context.threadId } == notFoundEx++modifier :: Thing -> Thing+modifier thing = thing { stuff = 2 * stuff thing }