Skip to content

Commit 1b36e97

Browse files
committed
io-sim: timeout fix
When io-sim is cancelling timeout not only remove it from 'PSQueue' but also update its state.
1 parent b5a910b commit 1b36e97

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
@@ -474,6 +474,7 @@ instance MonadTimer (IOSim s) where
474474
(\_ -> return Nothing) $
475475
bracket
476476
(forkIO $ do
477+
labelThisThread "<<timeout>>"
477478
fired <- atomically $ awaitTimeout t
478479
when fired $ throwTo pid (TimeoutException tid))
479480
(\pid' -> do
@@ -856,11 +857,22 @@ schedule thread@Thread{
856857
let thread' = thread { threadControl = ThreadControl k ctl }
857858
schedule thread' simstate
858859

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

865877
-- cancelling a negative timer is a no-op
866878
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)