Skip to content

Commit 3352255

Browse files
iohk-bors[bot]coot
andauthored
Merge #3076
3076: io-sim: timeout without deadlock r=dcoutts a=coot When io-sim is cancelling timeout not only remove it from 'PSQueue' but also update its state. The patch contains a minimal failing example which results in a deadlock if timeout state is not updated. Co-authored-by: Marcin Szamotulski <[email protected]>
2 parents a66e989 + 1b36e97 commit 3352255

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

io-sim/src/Control/Monad/IOSim/Internal.hs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ instance MonadTimer (IOSim s) where
478478
(\_ -> return Nothing) $
479479
bracket
480480
(forkIO $ do
481+
labelThisThread "<<timeout>>"
481482
fired <- atomically $ awaitTimeout t
482483
when fired $ throwTo pid (TimeoutException tid))
483484
(\pid' -> do
@@ -860,11 +861,22 @@ schedule thread@Thread{
860861
let thread' = thread { threadControl = ThreadControl k ctl }
861862
schedule thread' simstate
862863

863-
CancelTimeout (Timeout _tvar _tvar' tmid) k -> do
864+
CancelTimeout (Timeout tvar _tvar' tmid) k -> do
864865
let timers' = PSQ.delete tmid timers
865866
thread' = thread { threadControl = ThreadControl k ctl }
866-
trace <- schedule thread' simstate { timers = timers' }
867-
return (Trace time tid tlbl (EventTimerCancelled tmid) trace)
867+
written <- execAtomically' (runSTM $ writeTVar tvar TimeoutCancelled)
868+
(wakeup, wokeby) <- threadsUnblockedByWrites written
869+
mapM_ (\(SomeTVar var) -> unblockAllThreadsFromTVar var) written
870+
let (unblocked,
871+
simstate') = unblockThreads wakeup simstate
872+
trace <- schedule thread' simstate' { timers = timers' }
873+
return $ Trace time tid tlbl (EventTimerCancelled tmid)
874+
$ traceMany
875+
[ (time, tid', tlbl', EventTxWakeup vids)
876+
| tid' <- unblocked
877+
, let tlbl' = lookupThreadLabel tid' threads
878+
, let Just vids = Set.toList <$> Map.lookup tid' wokeby ]
879+
$ trace
868880

869881
-- cancelling a negative timer is a no-op
870882
CancelTimeout (NegativeTimeout _tmid) k -> do

io-sim/test/Test/IOSim.hs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ tests =
4242
, testProperty "timers (IOSim)" (withMaxSuccess 1000 prop_timers_ST)
4343
-- fails since we just use `threadDelay` to schedule timers in `IO`.
4444
, testProperty "timers (IO)" (expectFailure prop_timers_IO)
45+
, testProperty "timeout (IOSim): no deadlock"
46+
prop_timeout_no_deadlock_Sim
47+
, testProperty "timeout (IO): no deadlock"
48+
prop_timeout_no_deadlock_IO
4549
, testProperty "threadId order (IOSim)" (withMaxSuccess 1000 prop_threadId_order_order_Sim)
4650
, testProperty "forkIO order (IOSim)" (withMaxSuccess 1000 prop_fork_order_ST)
4751
, testProperty "order (IO)" (expectFailure prop_fork_order_IO)
@@ -852,6 +856,36 @@ prop_stm_referenceM (SomeTerm _tyrep t) = do
852856
return (r1 === r2)
853857

854858

859+
-- | Check that 'timeout' does not deadlock when executed with asynchronous
860+
-- exceptions uninterruptibly masked.
861+
--
862+
prop_timeout_no_deadlockM :: forall m. ( MonadFork m, MonadSTM m, MonadTimer m, MonadMask m )
863+
=> m Bool
864+
prop_timeout_no_deadlockM = do
865+
v <- registerDelay' 0.01
866+
r <- uninterruptibleMask_ $ timeout 0.02 $ do
867+
atomically $ do
868+
readTVar v >>= check
869+
return True
870+
case r of
871+
Nothing -> return False
872+
Just b -> return b
873+
where
874+
-- Like 'registerDelay', but does not require threaded RTS in the @m ~ IO@
875+
-- case.
876+
registerDelay' :: DiffTime -> m (StrictTVar m Bool)
877+
registerDelay' delta = do
878+
v <- newTVarIO False
879+
_ <- forkIO $ do
880+
threadDelay delta
881+
atomically (writeTVar v True)
882+
return v
883+
884+
prop_timeout_no_deadlock_Sim :: Bool
885+
prop_timeout_no_deadlock_Sim = runSimOrThrow prop_timeout_no_deadlockM
886+
887+
prop_timeout_no_deadlock_IO :: Property
888+
prop_timeout_no_deadlock_IO = ioProperty prop_timeout_no_deadlockM
855889

856890
--
857891
-- Utils

0 commit comments

Comments
 (0)