Skip to content

Commit f9194b1

Browse files
committed
Resolve PR comments
1 parent 5ef39f0 commit f9194b1

File tree

8 files changed

+90
-59
lines changed

8 files changed

+90
-59
lines changed

.github/workflows/cabal.project.local

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package io-classes
88

99
package strict-mvar
1010
ghc-options: -Werror
11-
flags: +asserts +checkmvarinvariant
1211

1312
package strict-stm
1413
ghc-options: -Werror

cabal.project

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,5 @@ package io-sim
3131
package io-classes
3232
flags: +asserts
3333

34-
package strict-mvar
35-
flags: +asserts
36-
3734
package strict-stm
3835
flags: +asserts

strict-mvar/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,46 @@ The `strict-mvar` package provides a strict interface to mutable variables
55
for `MVar`s implementations from both
66
[base](https://hackage.haskell.org/package/base-4.17.0.0/docs/Control-Concurrent-MVar.html)
77
and [io-sim](https://github.com/input-output-hk/io-sim).
8+
9+
## Checked and unchecked `StrictMVar`s
10+
11+
There are currently two variant implementations of `StrictMVar`s in this package:
12+
* `Control.Concurrent.Class.MonadMVar.Strict`
13+
* `Control.Concurrent.Class.MonadMVar.Strict.Checked`
14+
15+
The _unchecked_ module provides the simplest implementation of a `StrictMVar`: a
16+
light wrapper around lazy MVars that forces values to WHNF before they are put
17+
into the MVar. The _checked_ module does the exact same thing, but it has the
18+
additional feature that the user can provide an invariant that is checked each
19+
time a new value is placed inside the MVar. The two modules are drop-in
20+
replacements for one another: switching from `*.Strict` to `*.Strict.Checked`
21+
will enable invariant checking, while the converse will disable invariant
22+
checking. To facilitate drop-in replacement, both modules share the same
23+
interface, though in case of the `*.Strict` module, everything related to
24+
invariants will be ignored. This will be explicitly mentioned in the Haddock
25+
documentation of said definitions. For example:
26+
27+
```haskell
28+
-- | The given invariant will never be checked. 'newMVarWithInvariant' is a
29+
-- light wrapper around 'newMVar', and is only included here to ensure that the
30+
-- current module and "Control.Concurrent.Class.MonadMVar.Strict.Checked" are
31+
-- drop-in replacements for one another.
32+
newMVarWithInvariant :: MonadMVar m
33+
=> (a -> Maybe String)
34+
-> a
35+
-> m (StrictMVar m a)
36+
```
37+
38+
**Note:** though the two modules are drop-in replacements for one another, the
39+
`StrictMVar` type from `*.Strict` and the `StrictMVar` type from
40+
`*.Strict.Checked` do not share the same internal representation, and so they
41+
are distinct types.
42+
43+
## Guarantees for invariant checking
44+
45+
Although all functions that modify a checked `StrictMVar` will check the
46+
invariant, we do *not* guarantee that the value inside the `StrictMVar` always
47+
satisfies the invariant. Instead, we *do* guarantee that if the `StrictMVar` is
48+
updated with a value that does not satisfy the invariant, an exception is
49+
thrown. The reason for this weaker guarantee is that leaving an `MVar` empty can
50+
lead to very hard to debug "blocked indefinitely" problems.

strict-mvar/src/Control/Concurrent/Class/MonadMVar/Strict.hs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22
{-# LANGUAGE TypeFamilies #-}
33
{-# LANGUAGE TypeOperators #-}
44

5-
-- | This module corresponds to 'Control.Concurrent.MVar' in "base" package
5+
-- | This module corresponds to "Control.Concurrent.MVar" in the @base@ package.
66
--
7+
-- Use "Control.Concurrent.Class.MonadMVar.Strict.Checked" as a drop-in
8+
-- replacement for the current module in case you want to check invariants on
9+
-- the values inside 'StrictMVar's.
710
module Control.Concurrent.Class.MonadMVar.Strict
811
( -- * StrictMVar
912
StrictMVar
13+
, LazyMVar
1014
, castStrictMVar
1115
, toLazyMVar
1216
, fromLazyMVar
1317
, newEmptyMVar
18+
, newEmptyMVarWithInvariant
1419
, newMVar
20+
, newMVarWithInvariant
1521
, takeMVar
1622
, putMVar
1723
, readMVar
@@ -65,9 +71,28 @@ fromLazyMVar = StrictMVar
6571
newEmptyMVar :: MonadMVar m => m (StrictMVar m a)
6672
newEmptyMVar = fromLazyMVar <$> Lazy.newEmptyMVar
6773

74+
-- | The given invariant will never be checked. 'newEmptyMVarWithInvariant' is a
75+
-- light wrapper around 'newEmptyMVar', and is only included here to ensure that
76+
-- the current module and "Control.Concurrent.Class.MonadMVar.Strict.Checked"
77+
-- are drop-in replacements for one another.
78+
newEmptyMVarWithInvariant :: MonadMVar m
79+
=> (a -> Maybe String)
80+
-> m (StrictMVar m a)
81+
newEmptyMVarWithInvariant _inv = StrictMVar <$> Lazy.newEmptyMVar
82+
6883
newMVar :: MonadMVar m => a -> m (StrictMVar m a)
6984
newMVar !a = fromLazyMVar <$> Lazy.newMVar a
7085

86+
-- | The given invariant will never be checked. 'newMVarWithInvariant' is a
87+
-- light wrapper around 'newMVar', and is only included here to ensure that the
88+
-- current module and "Control.Concurrent.Class.MonadMVar.Strict.Checked" are
89+
-- drop-in replacements for one another.
90+
newMVarWithInvariant :: MonadMVar m
91+
=> (a -> Maybe String)
92+
-> a
93+
-> m (StrictMVar m a)
94+
newMVarWithInvariant _inv !a = StrictMVar <$> Lazy.newMVar a
95+
7196
takeMVar :: MonadMVar m => StrictMVar m a -> m a
7297
takeMVar = Lazy.takeMVar . mvar
7398

strict-mvar/src/Control/Concurrent/Class/MonadMVar/Strict/Checked.hs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
{-# LANGUAGE TypeFamilies #-}
55
{-# LANGUAGE TypeOperators #-}
66

7-
-- | This module corresponds to 'Control.Concurrent.MVar' in "base" package
7+
-- | This module corresponds to "Control.Concurrent.MVar" in the @base@ package.
88
--
9+
-- Use "Control.Concurrent.Class.MonadMVar.Strict" as a drop-in replacement for
10+
-- the current module in case you do /not/ want to check invariants on the
11+
-- values inside 'StrictMVar's.
912
module Control.Concurrent.Class.MonadMVar.Strict.Checked
1013
( -- * StrictMVar
1114
StrictMVar
15+
, LazyMVar
1216
, castStrictMVar
1317
, toLazyMVar
1418
, fromLazyMVar
@@ -87,17 +91,18 @@ fromLazyMVar = StrictMVar (const Nothing)
8791
newEmptyMVar :: MonadMVar m => m (StrictMVar m a)
8892
newEmptyMVar = fromLazyMVar <$> Lazy.newEmptyMVar
8993

90-
newEmptyMVarWithInvariant ::
91-
MonadMVar m
92-
=> (a -> Maybe String) -> m (StrictMVar m a)
94+
newEmptyMVarWithInvariant :: MonadMVar m
95+
=> (a -> Maybe String)
96+
-> m (StrictMVar m a)
9397
newEmptyMVarWithInvariant inv = StrictMVar inv <$> Lazy.newEmptyMVar
9498

9599
newMVar :: MonadMVar m => a -> m (StrictMVar m a)
96100
newMVar !a = fromLazyMVar <$> Lazy.newMVar a
97101

98-
newMVarWithInvariant ::
99-
MonadMVar m
100-
=> (a -> Maybe String) -> a -> m (StrictMVar m a)
102+
newMVarWithInvariant :: MonadMVar m
103+
=> (a -> Maybe String)
104+
-> a
105+
-> m (StrictMVar m a)
101106
newMVarWithInvariant inv !a =
102107
checkInvariant (inv a) $
103108
StrictMVar inv <$> Lazy.newMVar a
@@ -174,15 +179,10 @@ tryReadMVar v = Lazy.tryReadMVar (mvar v)
174179
-- Dealing with invariants
175180
--
176181

177-
-- | Check invariant (if enabled)
182+
-- | Check invariant
178183
--
179184
-- @checkInvariant mErr x@ is equal to @x@ if @mErr == Nothing@, and throws an
180185
-- error @err@ if @mErr == Just err@.
181186
checkInvariant :: HasCallStack => Maybe String -> a -> a
182-
183-
#if CHECK_MVAR_INVARIANT
184187
checkInvariant Nothing k = k
185-
checkInvariant (Just err) _ = error $ "Invariant violation: " ++ err
186-
#else
187-
checkInvariant _err k = k
188-
#endif
188+
checkInvariant (Just err) _ = error $ "StrictMVar invariant violation: " ++ err

strict-mvar/strict-mvar.cabal

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,6 @@ source-repository head
2424
location: https://github.com/input-output-hk/io-sim
2525
subdir: strict-mvar
2626

27-
flag checkmvarinvariant
28-
Description: Enable runtime invariant checks on StrictMVar
29-
Manual: True
30-
Default: False
31-
32-
flag asserts
33-
description: Enable assertions
34-
manual: False
35-
default: False
36-
3727
library
3828
hs-source-dirs: src
3929

@@ -50,12 +40,6 @@ library
5040
-Wpartial-fields
5141
-Widentities
5242

53-
if flag(asserts)
54-
ghc-options: -fno-ignore-asserts
55-
56-
if flag(checkmvarinvariant)
57-
cpp-options: -DCHECK_MVAR_INVARIANT
58-
5943
test-suite test
6044
type: exitcode-stdio-1.0
6145
hs-source-dirs: test

strict-mvar/test/Main.hs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,4 @@ import qualified Test.Control.Concurrent.Class.MonadMVar.Strict.Checked as Check
44
import Test.Tasty
55

66
main :: IO ()
7-
main = defaultMain $
8-
testGroup "Test" [
9-
testGroup "Control" [
10-
testGroup "Concurrent" [
11-
testGroup "Class" [
12-
testGroup "MonadMVar" [
13-
testGroup "Strict" [
14-
testGroup "Checked" [
15-
Checked.tests
16-
]
17-
]
18-
]
19-
]
20-
]
21-
]
22-
]
7+
main = defaultMain Checked.tests
Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
-- | Tests in this module depend on the @+checkmvarinvariant@ flag being
2-
-- enabled.
31
module Test.Control.Concurrent.Class.MonadMVar.Strict.Checked where
42

53
import Control.Concurrent.Class.MonadMVar.Strict.Checked
@@ -8,10 +6,12 @@ import Test.Tasty
86
import Test.Tasty.QuickCheck
97

108
tests :: TestTree
11-
tests = testGroup "Checked" [
12-
testProperty "prop_invariantShouldFail" prop_invariantShouldFail
13-
, testProperty "prop_invariantShouldNotFail" prop_invariantShouldNotFail
14-
]
9+
tests = testGroup "Test.Control.Concurrent.Class.MonadMVar.Strict" [
10+
testGroup "Checked" [
11+
testProperty "prop_invariantShouldFail" prop_invariantShouldFail
12+
, testProperty "prop_invariantShouldNotFail" prop_invariantShouldNotFail
13+
]
14+
]
1515

1616
-- | Invariant that checks whether an @Int@ is positive.
1717
invPositiveInt :: Int -> Maybe String
@@ -23,10 +23,8 @@ prop_invariantShouldFail :: Property
2323
prop_invariantShouldFail = once $ expectFailure $ monadicIO $ run $ do
2424
v <- newMVarWithInvariant invPositiveInt 0
2525
modifyMVar_ v (\x -> pure $ x - 1)
26-
pure ()
2726

2827
prop_invariantShouldNotFail :: Property
2928
prop_invariantShouldNotFail = monadicIO $ run $ do
3029
v <- newMVarWithInvariant invPositiveInt 0
3130
modifyMVar_ v (\x -> pure $ x + 1)
32-
pure ()

0 commit comments

Comments
 (0)