Hello community, here is the log from the commit of package ghc-async for openSUSE:Factory checked in at 2018-05-30 11:59:39 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ghc-async (Old) and /work/SRC/openSUSE:Factory/.ghc-async.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "ghc-async" Wed May 30 11:59:39 2018 rev:14 rq:607743 version:2.2.1 Changes: -------- --- /work/SRC/openSUSE:Factory/ghc-async/ghc-async.changes 2017-09-15 21:20:42.476355674 +0200 +++ /work/SRC/openSUSE:Factory/.ghc-async.new/ghc-async.changes 2018-05-30 12:23:27.517830961 +0200 @@ -1,0 +2,20 @@ +Mon May 14 17:02:11 UTC 2018 - psimons@suse.com + +- Update async to version 2.2.1. + - Add a Hashable instance for Async + - Documentation updates + - cancel now throws AsyncCancelled instead of ThreadKilled + - link and link2 now wrap exceptions in ExceptionInLinkedThread when + throwing to the linked thread. ExceptionInLinkedThread is a child + of AsyncException in the exception hierarchy, so this maintains the + invariant that exceptions thrown asynchronously should be + AsyncExceptions. + - link and link2 do not propagate AsyncCancelled, so it's now + possible to cancel a linked thread without cancelling yourself. + - Added linkOnly and link2Only to specify which exceptions should be + propagated,if you want something other than the default behaviour + of ignoring AsyncCancelled. + - new utility function compareAsyncs for comparing Asyncs of + different types. + +------------------------------------------------------------------- Old: ---- async-2.1.1.1.tar.gz New: ---- async-2.2.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ghc-async.spec ++++++ --- /var/tmp/diff_new_pack.TwEHkw/_old 2018-05-30 12:23:28.117812716 +0200 +++ /var/tmp/diff_new_pack.TwEHkw/_new 2018-05-30 12:23:28.121812595 +0200 @@ -1,7 +1,7 @@ # # spec file for package ghc-async # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %global pkg_name async %bcond_with tests Name: ghc-%{pkg_name} -Version: 2.1.1.1 +Version: 2.2.1 Release: 0 Summary: Run IO operations asynchronously and wait for their results License: BSD-3-Clause @@ -27,6 +27,7 @@ URL: https://hackage.haskell.org/package/%{pkg_name} Source0: https://hackage.haskell.org/package/%{pkg_name}-%{version}/%{pkg_name}-%{version}.tar.gz BuildRequires: ghc-Cabal-devel +BuildRequires: ghc-hashable-devel BuildRequires: ghc-rpm-macros BuildRequires: ghc-stm-devel %if %{with tests} @@ -80,7 +81,7 @@ %ghc_pkg_recache %files -f %{name}.files -%doc LICENSE +%license LICENSE %files devel -f %{name}-devel.files %doc changelog.md ++++++ async-2.1.1.1.tar.gz -> async-2.2.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/async-2.1.1.1/Control/Concurrent/Async.hs new/async-2.2.1/Control/Concurrent/Async.hs --- old/async-2.1.1.1/Control/Concurrent/Async.hs 2017-04-10 10:05:37.000000000 +0200 +++ new/async-2.2.1/Control/Concurrent/Async.hs 2018-02-04 17:37:42.000000000 +0100 @@ -1,7 +1,11 @@ -{-# LANGUAGE CPP, MagicHash, UnboxedTuples, RankNTypes #-} +{-# LANGUAGE CPP, MagicHash, UnboxedTuples, RankNTypes, + ExistentialQuantification #-} #if __GLASGOW_HASKELL__ >= 701 {-# LANGUAGE Trustworthy #-} #endif +#if __GLASGOW_HASKELL__ < 710 +{-# LANGUAGE DeriveDataTypeable #-} +#endif {-# OPTIONS -Wall #-} ----------------------------------------------------------------------------- @@ -94,8 +98,8 @@ withAsyncOnWithUnmask, -- ** Querying 'Async's - wait, poll, waitCatch, cancel, uninterruptibleCancel, cancelWith, - asyncThreadId, + wait, poll, waitCatch, asyncThreadId, + cancel, uninterruptibleCancel, cancelWith, AsyncCancelled(..), -- ** STM operations waitSTM, pollSTM, waitCatchSTM, @@ -113,7 +117,7 @@ waitBothSTM, -- ** Linking - link, link2, + link, link2, ExceptionInLinkedThread(..), -- * Convenient utilities race, race_, @@ -122,6 +126,7 @@ mapConcurrently_, forConcurrently_, replicateConcurrently, replicateConcurrently_, Concurrently(..), + compareAsyncs, ) where @@ -138,9 +143,13 @@ import Data.Monoid (Monoid(mempty,mappend)) import Data.Traversable #endif +#if __GLASGOW_HASKELL__ < 710 +import Data.Typeable +#endif #if MIN_VERSION_base(4,9,0) import Data.Semigroup (Semigroup((<>))) #endif +import Data.Hashable (Hashable(hashWithSalt)) import Data.IORef @@ -170,9 +179,16 @@ instance Ord (Async a) where Async a _ `compare` Async b _ = a `compare` b +instance Hashable (Async a) where + hashWithSalt salt (Async a _) = hashWithSalt salt a + instance Functor Async where fmap f (Async a w) = Async a (fmap (fmap f) w) +-- | Compare two 'Async's that may have different types +compareAsyncs :: Async a -> Async b -> Ordering +compareAsyncs (Async t1 _) (Async t2 _) = compare t1 t2 + -- | Spawn an asynchronous action in a separate thread. async :: IO a -> IO (Async a) async = inline asyncUsing rawForkIO @@ -212,11 +228,17 @@ -- @Async@ handle to the supplied function. When the function returns -- or throws an exception, 'uninterruptibleCancel' is called on the @Async@. -- --- > withAsync action inner = bracket (async action) uninterruptibleCancel inner +-- > withAsync action inner = mask $ \restore -> do +-- > a <- async (restore action) +-- > restore inner `finally` uninterruptibleCancel a -- -- This is a useful variant of 'async' that ensures an @Async@ is -- never left running unintentionally. -- +-- Note: a reference to the child thread is kept alive until the call +-- to `withAsync` returns, so nesting many `withAsync` calls requires +-- linear memory. +-- withAsync :: IO a -> (Async a -> IO b) -> IO b withAsync = inline withAsyncUsing rawForkIO @@ -312,11 +334,11 @@ pollSTM :: Async a -> STM (Maybe (Either SomeException a)) pollSTM (Async _ w) = (Just <$> w) `orElse` return Nothing --- | Cancel an asynchronous action by throwing the @ThreadKilled@ +-- | Cancel an asynchronous action by throwing the @AsyncCancelled@ -- exception to it, and waiting for the `Async` thread to quit. -- Has no effect if the 'Async' has already completed. -- --- > cancel a = throwTo (asyncThreadId a) ThreadKilled <* waitCatch a +-- > cancel a = throwTo (asyncThreadId a) AsyncCancelled <* waitCatch a -- -- Note that 'cancel' will not terminate until the thread the 'Async' -- refers to has terminated. This means that 'cancel' will block for @@ -330,7 +352,21 @@ -- and the handler is blocking. {-# INLINE cancel #-} cancel :: Async a -> IO () -cancel a@(Async t _) = throwTo t ThreadKilled <* waitCatch a +cancel a@(Async t _) = throwTo t AsyncCancelled <* waitCatch a + +-- | The exception thrown by `cancel` to terminate a thread. +data AsyncCancelled = AsyncCancelled + deriving (Show, Eq +#if __GLASGOW_HASKELL__ < 710 + ,Typeable +#endif + ) + +instance Exception AsyncCancelled where +#if __GLASGOW_HASKELL__ >= 708 + fromException = asyncExceptionFromException + toException = asyncExceptionToException +#endif -- | Cancel an asynchronous action -- @@ -406,7 +442,11 @@ waitEitherCatch :: Async a -> Async b -> IO (Either (Either SomeException a) (Either SomeException b)) -waitEitherCatch left right = atomically (waitEitherCatchSTM left right) +waitEitherCatch left right = + tryAgain $ atomically (waitEitherCatchSTM left right) + where + -- See: https://github.com/simonmar/async/issues/14 + tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f -- | A version of 'waitEitherCatch' that can be used inside an STM transaction. -- @@ -487,31 +527,83 @@ return (a,b) +-- ----------------------------------------------------------------------------- +-- Linking threads + +data ExceptionInLinkedThread = + forall a . ExceptionInLinkedThread (Async a) SomeException +#if __GLASGOW_HASKELL__ < 710 + deriving Typeable +#endif + +instance Show ExceptionInLinkedThread where + show (ExceptionInLinkedThread (Async t _) e) = + "ExceptionInLinkedThread " ++ show t ++ " " ++ show e + +instance Exception ExceptionInLinkedThread where +#if __GLASGOW_HASKELL__ >= 708 + fromException = asyncExceptionFromException + toException = asyncExceptionToException +#endif + -- | Link the given @Async@ to the current thread, such that if the -- @Async@ raises an exception, that exception will be re-thrown in --- the current thread. +-- the current thread, wrapped in 'ExceptionInLinkedThread'. +-- +-- 'link' ignores 'AsyncCancelled' exceptions thrown in the other thread, +-- so that it's safe to 'cancel' a thread you're linked to. If you want +-- different behaviour, use 'linkOnly'. -- link :: Async a -> IO () -link (Async _ w) = do +link = linkOnly (not . isCancel) + +-- | Link the given @Async@ to the current thread, such that if the +-- @Async@ raises an exception, that exception will be re-thrown in +-- the current thread. The supplied predicate determines which +-- exceptions in the target thread should be propagated to the source +-- thread. +-- +linkOnly + :: (SomeException -> Bool) -- ^ return 'True' if the exception + -- should be propagated, 'False' + -- otherwise. + -> Async a + -> IO () +linkOnly shouldThrow a = do me <- myThreadId void $ forkRepeat $ do - r <- atomically $ w - case r of - Left e -> throwTo me e - _ -> return () + r <- waitCatch a + case r of + Left e | shouldThrow e -> throwTo me (ExceptionInLinkedThread a e) + _otherwise -> return () -- | Link two @Async@s together, such that if either raises an --- exception, the same exception is re-thrown in the other @Async@. +-- exception, the same exception is re-thrown in the other @Async@, +-- wrapped in 'ExceptionInLinkedThread'. +-- +-- 'link2' ignores 'AsyncCancelled' exceptions, so that it's possible +-- to 'cancel' either thread without cancelling the other. If you +-- want different behaviour, use 'link2Only'. -- link2 :: Async a -> Async b -> IO () -link2 left@(Async tl _) right@(Async tr _) = +link2 = link2Only (not . isCancel) + +link2Only :: (SomeException -> Bool) -> Async a -> Async b -> IO () +link2Only shouldThrow left@(Async tl _) right@(Async tr _) = void $ forkRepeat $ do r <- waitEitherCatch left right case r of - Left (Left e) -> throwTo tr e - Right (Left e) -> throwTo tl e + Left (Left e) | shouldThrow e -> + throwTo tr (ExceptionInLinkedThread left e) + Right (Left e) | shouldThrow e -> + throwTo tl (ExceptionInLinkedThread right e) _ -> return () +isCancel :: SomeException -> Bool +isCancel e + | Just AsyncCancelled <- fromException e = True + | otherwise = False + -- ----------------------------------------------------------------------------- @@ -593,29 +685,47 @@ concurrently' left right collect = do done <- newEmptyMVar mask $ \restore -> do - lid <- forkIO $ restore (left >>= putMVar done . Right . Left) - `catchAll` (putMVar done . Left) - rid <- forkIO $ restore (right >>= putMVar done . Right . Right) - `catchAll` (putMVar done . Left) + -- Note: uninterruptibleMask here is because we must not allow + -- the putMVar in the exception handler to be interrupted, + -- otherwise the parent thread will deadlock when it waits for + -- the thread to terminate. + lid <- forkIO $ uninterruptibleMask_ $ + restore (left >>= putMVar done . Right . Left) + `catchAll` (putMVar done . Left) + rid <- forkIO $ uninterruptibleMask_ $ + restore (right >>= putMVar done . Right . Right) + `catchAll` (putMVar done . Left) count <- newIORef (2 :: Int) let takeDone = do + r <- takeMVar done -- interruptible -- Decrement the counter so we know how many takes are left. -- Since only the parent thread is calling this, we can -- use non-atomic modifications. + -- NB. do this *after* takeMVar, because takeMVar might be + -- interrupted. modifyIORef count (subtract 1) + return r - takeMVar done + let tryAgain f = f `catch` \BlockedIndefinitelyOnMVar -> f - let stop = do + stop = do -- kill right before left, to match the semantics of -- the version using withAsync. (#27) uninterruptibleMask_ $ do - killThread rid >> killThread lid - -- ensure the children are really dead count' <- readIORef count - replicateM_ count' (takeMVar done) - r <- restore (collect takeDone) `onException` stop + -- we only need to use killThread if there are still + -- children alive. Note: forkIO here is because the + -- child thread could be in an uninterruptible + -- putMVar. + when (count' > 0) $ + void $ forkIO $ do + throwTo rid AsyncCancelled + throwTo lid AsyncCancelled + -- ensure the children are really dead + replicateM_ count' (tryAgain $ takeMVar done) + + r <- collect (tryAgain $ takeDone) `onException` stop stop return r @@ -626,6 +736,9 @@ -- the original data structure with the arguments replaced by the -- results. -- +-- If any of the actions throw an exception, then all other actions are +-- cancelled and the exception is re-thrown. +-- -- For example, @mapConcurrently@ works with lists: -- -- > pages <- mapConcurrently getURL ["url1", "url2", "url3"] @@ -638,7 +751,7 @@ -- > pages <- forConcurrently ["url1", "url2", "url3"] $ \url -> getURL url -- -- @since 2.1.0 -forConcurrently :: Traversable t => t a -> (a -> IO b)-> IO (t b) +forConcurrently :: Traversable t => t a -> (a -> IO b) -> IO (t b) forConcurrently = flip mapConcurrently -- | `mapConcurrently_` is `mapConcurrently` with the return value discarded, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/async-2.1.1.1/async.cabal new/async-2.2.1/async.cabal --- old/async-2.1.1.1/async.cabal 2017-04-10 10:05:37.000000000 +0200 +++ new/async-2.2.1/async.cabal 2018-02-04 17:37:42.000000000 +0100 @@ -1,5 +1,5 @@ name: async -version: 2.1.1.1 +version: 2.2.1 -- don't forget to update ./changelog.md! synopsis: Run IO operations asynchronously and wait for their results @@ -34,7 +34,7 @@ cabal-version: >=1.10 homepage: https://github.com/simonmar/async bug-reports: https://github.com/simonmar/async/issues -tested-with: GHC==7.11.*, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2, GHC==7.2.2, GHC==7.0.4 +tested-with: GHC==8.2.2, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2, GHC==7.2.2, GHC==7.0.4 extra-source-files: changelog.md @@ -50,15 +50,48 @@ if impl(ghc>=7.1) other-extensions: Trustworthy exposed-modules: Control.Concurrent.Async - build-depends: base >= 4.3 && < 4.11, stm >= 2.2 && < 2.5 + build-depends: base >= 4.3 && < 4.12, hashable >= 1.1.1.0 && < 1.3, stm >= 2.2 && < 2.5 test-suite test-async default-language: Haskell2010 type: exitcode-stdio-1.0 hs-source-dirs: test main-is: test-async.hs - build-depends: base >= 4.3 && < 4.11, + build-depends: base >= 4.3 && < 4.12, async, + stm, test-framework, test-framework-hunit, HUnit + +flag bench + default: False + +executable concasync + if !flag(bench) + buildable: False + default-language: Haskell2010 + hs-source-dirs: bench + main-is: concasync.hs + build-depends: base, async, stm + ghc-options: -O2 + +executable conccancel + if !flag(bench) + buildable: False + default-language: Haskell2010 + hs-source-dirs: bench + main-is: conccancel.hs + build-depends: base, async, stm + ghc-options: -O2 -threaded + +executable race + if !flag(bench) + buildable: False + default-language: Haskell2010 + hs-source-dirs: bench + main-is: race.hs + build-depends: base, async, stm + ghc-options: -O2 -threaded + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/async-2.1.1.1/bench/concasync.hs new/async-2.2.1/bench/concasync.hs --- old/async-2.1.1.1/bench/concasync.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/async-2.2.1/bench/concasync.hs 2018-02-04 17:37:42.000000000 +0100 @@ -0,0 +1,13 @@ +import Control.Concurrent.Async +import System.Environment +import Control.Monad +import Control.Concurrent + +main = runInUnboundThread $ do + [n] <- fmap (fmap read) getArgs + replicateM_ n $ concurrently (return 1) (return 2) + +concurrently' left right = + withAsync left $ \a -> + withAsync right $ \b -> + waitBoth a b diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/async-2.1.1.1/bench/conccancel.hs new/async-2.2.1/bench/conccancel.hs --- old/async-2.1.1.1/bench/conccancel.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/async-2.2.1/bench/conccancel.hs 2018-02-04 17:37:42.000000000 +0100 @@ -0,0 +1,11 @@ +import Control.Exception +import Control.Concurrent.Async +import System.Environment +import Control.Monad +import Control.Concurrent + +main = runInUnboundThread $ do + [n] <- fmap (fmap read) getArgs + runConcurrently $ traverse Concurrently $ + replicate n (threadDelay 1000000) ++ [throwIO (ErrorCall "oops")] + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/async-2.1.1.1/changelog.md new/async-2.2.1/changelog.md --- old/async-2.1.1.1/changelog.md 2017-04-10 10:05:37.000000000 +0200 +++ new/async-2.2.1/changelog.md 2018-02-04 17:37:42.000000000 +0100 @@ -1,3 +1,25 @@ +## Changes in 2.2.1: + + - Add a Hashable instance for Async + - Bump upper bounds + - Documentation updates + +## Changes in 2.2: + - cancel now throws AsyncCancelled instead of ThreadKilled + - link and link2 now wrap exceptions in ExceptionInLinkedThread when + throwing to the linked thread. ExceptionInLinkedThread is a child + of AsyncException in the exception hierarchy, so this maintains the + invariant that exceptions thrown asynchronously should be + AsyncExceptions. + - link and link2 do not propagate AsyncCancelled, so it's now + possible to cancel a linked thread without cancelling yourself. + - Added linkOnly and link2Only to specify which exceptions should be + propagated,if you want something other than the default behaviour + of ignoring AsyncCancelled. + - new utility function compareAsyncs for comparing Asyncs of + different types. + - Add a `Hashable` instance for `Async a` + ## Changes in 2.1.1.1: - Make 'cancelWith' wait for the cancelled thread to terminate, like 'cancel' - Updates to dependency bounds for GHC 8.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/async-2.1.1.1/test/test-async.hs new/async-2.2.1/test/test-async.hs --- old/async-2.1.1.1/test/test-async.hs 2017-04-10 10:05:37.000000000 +0200 +++ new/async-2.2.1/test/test-async.hs 2018-02-04 17:37:42.000000000 +0100 @@ -6,6 +6,7 @@ import Test.HUnit +import Control.Concurrent.STM import Control.Concurrent.Async import Control.Exception import Data.IORef @@ -29,6 +30,7 @@ , testGroup "async_cancel_rep" $ replicate 1000 $ testCase "async_cancel" async_cancel + , testCase "async_cancelmany" async_cancelmany , testCase "async_poll" async_poll , testCase "async_poll2" async_poll2 , testCase "withasync_waitCatch_blocked" withasync_waitCatch_blocked @@ -43,6 +45,10 @@ , testCase "concurrently_" case_concurrently_ , testCase "replicateConcurrently_" case_replicateConcurrently , testCase "replicateConcurrently" case_replicateConcurrently_ + , testCase "link1" case_link1 + , testCase "link2" case_link2 + , testCase "link1_cancel" case_link1cancel + , testCase "concurrently_deadlock" case_concurrently_deadlock ] value = 42 :: Int @@ -90,7 +96,7 @@ a <- withAsync (threadDelay 1000000) $ return r <- waitCatch a case r of - Left e -> fromException e @?= Just ThreadKilled + Left e -> fromException e @?= Just AsyncCancelled Right _ -> assertFailure "" async_cancel :: Assertion @@ -102,6 +108,18 @@ Left e -> fromException e @?= Just TestException Right r -> r @?= value +async_cancelmany :: Assertion -- issue 59 +async_cancelmany = do + r <- newIORef [] + a <- async $ forConcurrently_ ['a'..'z'] $ \c -> + delay 2 `finally` atomicModifyIORef r (\i -> (c:i,())) + delay 1 + cancel a + v <- readIORef r + assertEqual "cancelmany" 26 (length v) + where + delay sec = threadDelay (sec * 1000000) + async_poll :: Assertion async_poll = do a <- async (threadDelay 1000000) @@ -236,3 +254,86 @@ () <- replicateConcurrently_ 100 action resVal <- readIORef ref resVal @?= 100 + +case_link1 :: Assertion +case_link1 = do + m1 <- newEmptyMVar + m2 <- newEmptyMVar + let ex = ErrorCall "oops" + a <- async $ do takeMVar m1; throwIO ex; putMVar m2 () + link a + e <- try $ (do + putMVar m1 () + takeMVar m2) + assertBool "link1" $ + case e of + Left (ExceptionInLinkedThread a' e') -> + compareAsyncs a' a == EQ && + case fromException e' of + Just (ErrorCall s) -> s == "oops" + _otherwise -> False + _other -> False + +case_link2 :: Assertion +case_link2 = do + let + setup = do + m1 <- newEmptyMVar + m2 <- newEmptyMVar + let ex1 = ErrorCall "oops1"; ex2 = ErrorCall "oops2" + a <- async $ do takeMVar m1; throwIO ex1 + b <- async $ do takeMVar m2; throwIO ex2 + link2 a b + return (m1,m2,a,b) + + (m1,m2,a,b) <- setup + e <- try $ do + putMVar m1 () + wait b + putMVar m2 () -- ensure the other thread is not deadlocked + assertBool "link2a" $ + case e of + Left (ExceptionInLinkedThread a' e') -> + compareAsyncs a' a == EQ && + case fromException e' of + Just (ErrorCall s) -> s == "oops1" + _otherwise -> False + _other -> False + + (m1,m2,a,b) <- setup + e <- try $ do + putMVar m2 () + wait a + putMVar m1 () -- ensure the other thread is not deadlocked + assertBool "link2b" $ + case e of + Left (ExceptionInLinkedThread a' e') -> + compareAsyncs a' b == EQ && + case fromException e' of + Just (ErrorCall s) -> s == "oops2" + _otherwise -> False + _other -> False + +case_link1cancel :: Assertion +case_link1cancel = do + m1 <- newEmptyMVar + let ex = ErrorCall "oops" + a <- async $ do takeMVar m1 + link a + e <- try $ do cancel a; wait a + putMVar m1 () + assertBool "link1cancel" $ + case e of + Left AsyncCancelled -> True -- should not be ExceptionInLinkedThread + _other -> False + +-- See Issue #62 +case_concurrently_deadlock :: Assertion +case_concurrently_deadlock = do + tvar <- newTVarIO False :: IO (TVar Bool) + e <- try $ void $ join (concurrently) (atomically $ readTVar tvar >>= check) + -- should throw BlockedIndefinitelyOnSTM not BlockedIndefinitelyOnMVar + assertBool "concurrently_deadlock" $ + case e of + Left BlockedIndefinitelyOnSTM{} -> True + _other -> False