Skip to content

Commit

Permalink
Prevent STM waking up threads blocked on threadDelay
Browse files Browse the repository at this point in the history
The first argument of `unblockThreads`, `onlySTM`, indicates that only threads
actually blocked on STM are woken up. This change correctly sets it to `True`
when necessary.

Co-authored-by: Armando Santos <[email protected]>
  • Loading branch information
amesgen and bolt12 committed Feb 15, 2024
1 parent b6774b5 commit d143bd5
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 4 deletions.
2 changes: 2 additions & 0 deletions io-sim/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## next version (1.4.1.0)

* Prevent STM waking up threads blocked on `threadDelay`.

### Non-breaking changes

* QuickCheck monadic combinators: `monadicIOSim`, `monadicIOSim_` and `runIOSimGen`.
Expand Down
6 changes: 4 additions & 2 deletions io-sim/src/Control/Monad/IOSim/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,10 @@ reschedule !simstate@SimState{ threads, timers, curTime = time } =

-- Check all fired threadDelays
let wakeupThreadDelay = [ (tid, tmid) | TimerThreadDelay tid tmid <- fired ]
wakeup = fst `fmap` wakeupThreadDelay ++ wakeupSTM
(_, !simstate') = unblockThreads False wakeup simstate
!simstate' =
snd . unblockThreads False (fst `fmap` wakeupThreadDelay)
. snd . unblockThreads True wakeupSTM
$ simstate

-- For each 'timeout' action where the timeout has fired, start a
-- new thread to execute throwTo to interrupt the action.
Expand Down
6 changes: 4 additions & 2 deletions io-sim/src/Control/Monad/IOSimPOR/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,9 +1033,11 @@ reschedule simstate@SimState{ threads, timers, curTime = time, races } =
mapM_ (\(SomeTVar tvar) -> unblockAllThreadsFromTVar tvar) written

let wakeupThreadDelay = [ (tid, tmid) | TimerThreadDelay tid tmid <- fired ]
wakeup = fst `fmap` wakeupThreadDelay ++ wakeupSTM
-- TODO: the vector clock below cannot be right, can it?
(_, !simstate') = unblockThreads False bottomVClock wakeup simstate
!simstate' =
snd . unblockThreads False bottomVClock (fst `fmap` wakeupThreadDelay)
. snd . unblockThreads True bottomVClock wakeupSTM
$ simstate

-- For each 'timeout' action where the timeout has fired, start a
-- new thread to execute throwTo to interrupt the action.
Expand Down
29 changes: 29 additions & 0 deletions io-sim/test/Test/Control/Monad/IOSim.hs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ tests =
, testProperty "async exceptions 2" unit_timeouts_and_async_exceptions_2
, testProperty "async exceptions 3" unit_timeouts_and_async_exceptions_3
, testProperty "threadDelay and STM" unit_threadDelay_and_stm
, testProperty "{register,thread}Delay" unit_registerDelay_threadDelay
, testProperty "throwTo and STM" unit_throwTo_and_stm
]
, testProperty "threadId order (IOSim)" (withMaxSuccess 1000 prop_threadId_order_order_Sim)
Expand Down Expand Up @@ -1159,6 +1160,34 @@ unit_threadDelay_and_stm =

return (t1 `diffTime` t0 === delay)

unit_registerDelay_threadDelay :: Property
unit_registerDelay_threadDelay =
let trace = runSimTrace experiment in
counterexample (ppTrace_ trace)
. either (\e -> counterexample (show e) False) id
. traceResult False
$ trace
where
experiment :: IOSim s Property
experiment = do
v0 <- registerDelay 2
v1 <- newTVarIO False

_ <- forkIO $ do
threadDelay 1
atomically $ writeTVar v1 True

atomically $ do
b0 <- LazySTM.readTVar v0
b1 <- readTVar v1
check $ b0 || b1

let delay = 2
t0 <- getMonotonicTime
threadDelay delay
t1 <- getMonotonicTime

return (t1 `diffTime` t0 === delay)

-- | Verify that a thread blocked on `throwTo` is not unblocked by an STM
-- transaction.
Expand Down
30 changes: 30 additions & 0 deletions io-sim/test/Test/Control/Monad/IOSimPOR.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ tests =
, testProperty "timeout" prop_timeout
, testProperty "timeouts" prop_timeouts
, testProperty "stacked timeouts" prop_stacked_timeouts
, testProperty "{register,thread}Delay" unit_registerDelay_threadDelay
]
, testProperty "threadId order (IOSim)" (withMaxSuccess 1000 prop_threadId_order_order_Sim)
, testProperty "forkIO order (IOSim)" (withMaxSuccess 1000 prop_fork_order_ST)
Expand Down Expand Up @@ -970,6 +971,35 @@ prop_stacked_timeouts timeout0 timeout1 actionDuration =
| otherwise -- i.e. timeout0 >= timeout1
= Just Nothing

unit_registerDelay_threadDelay :: Property
unit_registerDelay_threadDelay =
exploreSimTrace id experiment $ \_ trace ->
counterexample (ppTrace_ trace)
. either (\e -> counterexample (show e) False) id
. traceResult False
$ trace
where
experiment :: IOSim s Property
experiment = do
v0 <- registerDelay 2
v1 <- newTVarIO False

_ <- forkIO $ do
threadDelay 1
atomically $ writeTVar v1 True

atomically $ do
b0 <- readTVar v0
b1 <- readTVar v1
check $ b0 || b1

let delay = 2
t0 <- getMonotonicTime
threadDelay delay
t1 <- getMonotonicTime

return (t1 `diffTime` t0 === delay)

unit_timeouts_and_async_exceptions_1 :: Property
unit_timeouts_and_async_exceptions_1 =
exploreSimTrace id experiment $ \_ trace ->
Expand Down

0 comments on commit d143bd5

Please sign in to comment.