@@ -76,12 +76,13 @@ import Control.Monad.Class.MonadSTM hiding (STM)
76
76
import Control.Monad.Class.MonadSTM.Internal (TMVarDefault (TMVar ))
77
77
import Control.Monad.Class.MonadThrow hiding (getMaskingState )
78
78
import Control.Monad.Class.MonadTime
79
- import Control.Monad.Class.MonadTimer.SI (TimeoutState (.. ))
79
+ import Control.Monad.Class.MonadTimer.SI (TimeoutState (.. ), DiffTime , diffTimeToMicrosecondsAsInt , microsecondsAsIntToDiffTime )
80
80
81
81
import Control.Monad.IOSim.InternalTypes
82
82
import Control.Monad.IOSim.Types hiding (SimEvent (SimPOREvent ),
83
83
Trace (SimPORTrace ))
84
84
import Control.Monad.IOSim.Types (SimEvent )
85
+ import System.Random (StdGen , randomR , split )
85
86
86
87
--
87
88
-- Simulation interpreter
@@ -150,19 +151,21 @@ data SimState s a = SimState {
150
151
-- | list of clocks
151
152
clocks :: ! (Map ClockId UTCTime ),
152
153
nextVid :: ! TVarId , -- ^ next unused 'TVarId'
153
- nextTmid :: ! TimeoutId -- ^ next unused 'TimeoutId'
154
+ nextTmid :: ! TimeoutId , -- ^ next unused 'TimeoutId'
155
+ stdGen :: ! StdGen
154
156
}
155
157
156
- initialState :: SimState s a
157
- initialState =
158
+ initialState :: StdGen -> SimState s a
159
+ initialState stdGen =
158
160
SimState {
159
161
runqueue = mempty ,
160
162
threads = Map. empty,
161
163
curTime = Time 0 ,
162
164
timers = PSQ. empty,
163
165
clocks = Map. singleton (ClockId [] ) epoch1970,
164
166
nextVid = TVarId 0 ,
165
- nextTmid = TimeoutId 0
167
+ nextTmid = TimeoutId 0 ,
168
+ stdGen = stdGen
166
169
}
167
170
where
168
171
epoch1970 = UTCTime (fromGregorian 1970 1 1 ) 0
@@ -189,6 +192,42 @@ invariant Nothing SimState{runqueue,threads,clocks} =
189
192
timeSinceEpoch :: Time -> NominalDiffTime
190
193
timeSinceEpoch (Time t) = fromRational (toRational t)
191
194
195
+ -- | This function receives a delay and adds jitter to it. The amount of
196
+ -- jitter added is proportional to how large the delay is so to not greatly
197
+ -- affect the indended behaviour of the function that calls it.
198
+ --
199
+ -- This function is used in order to introduce random delays between
200
+ -- concurrent threads so that different thread schedulings might be found.
201
+ --
202
+ -- This approach is nice because, since time is perfect (due to infinite
203
+ -- processing power of IOSim), IOSim will be able to introduce slight delays
204
+ -- that might lead to threads being scheduled differently.
205
+ --
206
+ -- Note that this only enables IOSim to explore different thread schedules for
207
+ -- concurrent threads blocked on 'threadDelay'. For threads blocked on STM
208
+ -- IOSim employs a way to awake threads in a pseudo random way.
209
+ --
210
+ -- Also note that it is safe to add jitter to 'threadDelay' because we only
211
+ -- have to guarantee that the thread is not woken up earlier than the delay
212
+ -- specified.
213
+ --
214
+ jitterDelay :: StdGen -> DiffTime -> DiffTime
215
+ jitterDelay stdGen d =
216
+ let -- Convert delay from DiffTime to picoseconds
217
+ delayInMicrosecondsAsInt = diffTimeToMicrosecondsAsInt d
218
+
219
+ -- Define the maximum jitter as a percentage of the delay
220
+ -- For example, 10% of the delay
221
+ maxJitter = delayInMicrosecondsAsInt `div` 10
222
+
223
+ -- Generate a random jitter value within the range
224
+ (jitterInMicrosecondsAsInt, _) = randomR (0 , maxJitter) stdGen
225
+
226
+ -- Convert jitter back to DiffTime
227
+ jitter = microsecondsAsIntToDiffTime jitterInMicrosecondsAsInt
228
+
229
+ in -- Add jitter to the original delay
230
+ d + jitter
192
231
193
232
-- | Schedule / run a thread.
194
233
--
@@ -205,7 +244,8 @@ schedule !thread@Thread{
205
244
timers,
206
245
clocks,
207
246
nextVid, nextTmid,
208
- curTime = time
247
+ curTime = time,
248
+ stdGen
209
249
} =
210
250
invariant (Just thread) simstate $
211
251
case action of
@@ -390,12 +430,15 @@ schedule !thread@Thread{
390
430
! tvar <- execNewTVar nextVid
391
431
(Just $! " <<timeout " ++ show (unTimeoutId nextTmid) ++ " >>" )
392
432
False
393
- let ! expiry = d `addTime` time
433
+ let ! expiry = jitterDelay stdGen d `addTime` time
394
434
! timers' = PSQ. insert nextTmid expiry (TimerRegisterDelay tvar) timers
395
435
! thread' = thread { threadControl = ThreadControl (k tvar) ctl }
436
+ (_, ! stdGen') = split stdGen
396
437
trace <- schedule thread' simstate { timers = timers'
397
438
, nextVid = succ nextVid
398
- , nextTmid = succ nextTmid }
439
+ , nextTmid = succ nextTmid
440
+ , stdGen = stdGen'
441
+ }
399
442
return (SimTrace time tid tlbl
400
443
(EventRegisterDelayCreated nextTmid nextVid expiry) trace)
401
444
@@ -409,11 +452,14 @@ schedule !thread@Thread{
409
452
trace)
410
453
411
454
ThreadDelay d k -> do
412
- let ! expiry = d `addTime` time
455
+ let ! expiry = jitterDelay stdGen d `addTime` time
413
456
! timers' = PSQ. insert nextTmid expiry (TimerThreadDelay tid nextTmid) timers
414
457
! thread' = thread { threadControl = ThreadControl (Return () ) (DelayFrame nextTmid k ctl) }
458
+ (_, ! stdGen') = split stdGen
415
459
! trace <- deschedule (Blocked BlockedOnDelay ) thread' simstate { timers = timers'
416
- , nextTmid = succ nextTmid }
460
+ , nextTmid = succ nextTmid
461
+ , stdGen = stdGen'
462
+ }
417
463
return (SimTrace time tid tlbl (EventThreadDelay nextTmid expiry) trace)
418
464
419
465
-- we treat negative timers as cancelled ones; for the record we put
@@ -432,13 +478,16 @@ schedule !thread@Thread{
432
478
! tvar <- execNewTVar nextVid
433
479
(Just $! " <<timeout-state " ++ show (unTimeoutId nextTmid) ++ " >>" )
434
480
TimeoutPending
435
- let ! expiry = d `addTime` time
481
+ let ! expiry = jitterDelay stdGen d `addTime` time
436
482
! t = Timeout tvar nextTmid
437
483
! timers' = PSQ. insert nextTmid expiry (Timer tvar) timers
438
484
! thread' = thread { threadControl = ThreadControl (k t) ctl }
485
+ (_, ! stdGen') = split stdGen
439
486
trace <- schedule thread' simstate { timers = timers'
440
487
, nextVid = succ nextVid
441
- , nextTmid = succ nextTmid }
488
+ , nextTmid = succ nextTmid
489
+ , stdGen = stdGen'
490
+ }
442
491
return (SimTrace time tid tlbl (EventTimerCreated nextTmid nextVid expiry) trace)
443
492
444
493
CancelTimeout (Timeout tvar tmid) k -> do
@@ -861,9 +910,9 @@ forkTimeoutInterruptThreads timeoutExpired simState =
861
910
where
862
911
-- we launch a thread responsible for throwing an AsyncCancelled exception
863
912
-- to the thread which timeout expired
864
- throwToThread :: [(Thread s a , TMVar (IOSim s ) IOSimThreadId )]
913
+ throwToThread :: [(Thread s a , TMVar (IOSim s ) IOSimThreadId )]
865
914
866
- (simState', throwToThread) = List. mapAccumR fn simState timeoutExpired
915
+ (simState', throwToThread) = List. mapAccumR fn simState timeoutExpired
867
916
where
868
917
fn :: SimState s a
869
918
-> (IOSimThreadId , TimeoutId , TMVar (IOSim s ) IOSimThreadId )
@@ -997,8 +1046,8 @@ lookupThreadLabel tid threads = join (threadLabel <$> Map.lookup tid threads)
997
1046
-- computation with 'Control.Monad.IOSim.traceEvents'. A slightly more
998
1047
-- convenient way is exposed by 'Control.Monad.IOSim.runSimTrace'.
999
1048
--
1000
- runSimTraceST :: forall s a . IOSim s a -> ST s (SimTrace a )
1001
- runSimTraceST mainAction = schedule mainThread initialState
1049
+ runSimTraceST :: forall s a . StdGen -> IOSim s a -> ST s (SimTrace a )
1050
+ runSimTraceST stdGen mainAction = schedule mainThread ( initialState stdGen)
1002
1051
where
1003
1052
mainThread =
1004
1053
Thread {
0 commit comments