Skip to content

Commit cce1b61

Browse files
committed
si-timers: round difftime to microseconds in IOSim
This makes `threadDelay` and `MonadTimer` from `si-timers` sublibrary behave the same way for `IOSim` and `IO`.
1 parent 5683864 commit cce1b61

File tree

5 files changed

+58
-6
lines changed

5 files changed

+58
-6
lines changed

io-classes/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
### Breaking changes
66

7-
+* Changed `Time` show instance, which now is designed for pasting
8-
+ counterexamples from terminal to an editor.
7+
* Changed `Time` show instance, which now is designed for pasting
8+
* counterexamples from terminal to an editor.
99

1010
### Non-breaking changes
1111

1212
* Improved performance of `tryReadTBQueueDefault`.
1313
* Added module `Control.Monad.Class.MonadUnique` generalising `Data.Unique`.
1414
* mtl: Added module `Control.Monad.Class.MonadUnique.Trans` providing monad transformer instances for `MonadUnique`.
15+
* Added `roundDiffTimeToMicroseconds` utility function to `si-timers` package (in the `MonadTimer.SI` module).
1516

1617
## 1.8.0.1
1718

io-classes/si-timers/src/Control/Monad/Class/MonadTimer/SI.hs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module Control.Monad.Class.MonadTimer.SI
55
-- * Auxiliary functions
66
, diffTimeToMicrosecondsAsInt
77
, microsecondsAsIntToDiffTime
8+
, roundDiffTimeToMicroseconds
89
-- * Re-exports
910
, DiffTime
1011
, MonadFork
@@ -54,9 +55,24 @@ diffTimeToMicrosecondsAsInt d =
5455
microsecondsAsIntToDiffTime :: Int -> DiffTime
5556
microsecondsAsIntToDiffTime = (/ 1_000_000) . fromIntegral
5657

58+
-- | Round to microseconds.
59+
--
60+
-- For negative diff times it rounds towards negative infinity, which is
61+
-- desirable for `MonadTimer` API.
62+
--
63+
roundDiffTimeToMicroseconds :: DiffTime -> DiffTime
64+
roundDiffTimeToMicroseconds d = fromIntegral usec / 1_000_000
65+
where
66+
-- microseconds
67+
usec :: Integer
68+
usec = diffTimeToPicoseconds d `div` 1_000_000
69+
70+
5771
class ( MonadTimer.MonadDelay m
5872
, MonadMonotonicTime m
5973
) => MonadDelay m where
74+
-- | All instances SHOULD round delays down to the nearest microsecond so the
75+
-- behaviour matches the `IO` instance.
6076
threadDelay :: DiffTime -> m ()
6177

6278
-- | Thread delay. This implementation will not over- or underflow.
@@ -68,6 +84,9 @@ class ( MonadTimer.MonadDelay m
6884
-- For delays smaller than `minBound :: Int` seconds, `minBound :: Int` will be
6985
-- used instead.
7086
--
87+
-- NOTE: since `MonadTimer.threadDelay` uses microsecond precision (as does
88+
-- GHC), so does this instance.
89+
--
7190
instance MonadDelay IO where
7291
threadDelay :: forall m.
7392
MonadDelay m
@@ -103,6 +122,11 @@ instance MonadDelay IO where
103122
instance MonadDelay m => MonadDelay (ReaderT r m) where
104123
threadDelay = lift . threadDelay
105124

125+
-- | `MonadTimer` API based on SI units (seconds).
126+
--
127+
-- NOTE: all instances SHOULD round delays down to the nearest microsecond so
128+
-- the behaviour matches the `IO` instance.
129+
--
106130
class ( MonadTimer.MonadTimer m
107131
, MonadMonotonicTime m
108132
) => MonadTimer m where

io-classes/si-timers/test/Test/MonadTimer.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ tests =
1717
prop_diffTimeToMicrosecondsAsIntLeftInverse
1818
, testProperty "diffTimeToMicroseconds right inverse"
1919
prop_diffTimeToMicrosecondsAsIntRightInverse
20+
, testProperty "roundToMicroseconds"
21+
prop_roundDiffTimeToMicroseconds
2022
]
2123

2224
newtype IntDistr = IntDistr Int
@@ -88,3 +90,21 @@ prop_diffTimeToMicrosecondsAsIntRightInverse (DiffTimeDistr a) =
8890
-> "large"
8991
| otherwise
9092
-> "average"
93+
94+
95+
prop_roundDiffTimeToMicroseconds :: DiffTimeDistr -> Property
96+
prop_roundDiffTimeToMicroseconds (DiffTimeDistr d) =
97+
-- rounded is less or equal to d
98+
--
99+
-- NOTE: this guarantees that if `d < 0` then `d' < 0` which is
100+
-- important for `MonadTimer (IOSim s)` instance.
101+
d' <= d
102+
.&&.
103+
-- difference is less than 1 microsecond
104+
abs (d - d') < 0.000_001
105+
.&&.
106+
-- rounded has no fractional microseconds
107+
case properFraction (d' * 1_000_000) of
108+
(_ :: Integer, f) -> f === 0
109+
where
110+
d' = roundDiffTimeToMicroseconds d

io-sim/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
which are based on it: `runSim`, `runSimOrThrow`, or `runSimStrictShutdown`)
1313
with `within` or `discardAfter` from `QuickCheck`. See the test suite how to
1414
use `discardAfter` with `IOSim`.
15+
* Round `si-timers` API (`MonadDelay`, `MonadTimer`) to microsecond to match
16+
`IO` behaviour.
1517

1618
## 1.8.0.1
1719

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,8 @@ instance MonadDelay (IOSim s) where
725725

726726
instance SI.MonadDelay (IOSim s) where
727727
threadDelay d =
728-
IOSim $ oneShot $ \k -> ThreadDelay d (k ())
728+
IOSim $ oneShot $ \k -> ThreadDelay (SI.roundDiffTimeToMicroseconds d)
729+
(k ())
729730

730731
data Timeout s = Timeout !(TVar s TimeoutState) !TimeoutId
731732
-- ^ a timeout
@@ -765,11 +766,15 @@ instance SI.MonadTimer (IOSim s) where
765766
timeout d action
766767
| d < 0 = Just <$> action
767768
| d == 0 = return Nothing
768-
| otherwise = IOSim $ oneShot $ \k -> StartTimeout d (runIOSim action) k
769+
| otherwise = IOSim $ oneShot $ \k ->
770+
StartTimeout (SI.roundDiffTimeToMicroseconds d)
771+
(runIOSim action)
772+
k
769773

770-
registerDelay d = IOSim $ oneShot $ \k -> RegisterDelay d k
774+
registerDelay d = IOSim $ oneShot $ \k ->
775+
RegisterDelay (SI.roundDiffTimeToMicroseconds d) k
771776
registerDelayCancellable d = do
772-
t <- newTimeout d
777+
t <- newTimeout (SI.roundDiffTimeToMicroseconds d)
773778
return (readTimeout t, cancelTimeout t)
774779

775780
newtype TimeoutException = TimeoutException TimeoutId deriving Eq

0 commit comments

Comments
 (0)