-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathTestAtomics.hs
247 lines (207 loc) · 7.66 KB
/
TestAtomics.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import Control.Concurrent
import Data.Atomics.Counter
import Data.Atomics
import Data.IORef
import Control.Monad
import GHC.Conc
import Control.Exception(evaluate)
import qualified Data.Set as Set
import Data.List
main = do
testCounterOverflow
testConsistentSuccessFailure
counterTest
{-
testCAS_ABA_0
testCAS_ABA_0_modAfterAtomicMod
testCAS_ABA_0_modBefAtomicMod
testCAS_ABA_1
testCAS_ABA_2
testCAS_ABA_3
-}
cHUNK_SIZE = 32
maxInt = maxBound :: Int
minInt = minBound :: Int
testCounterOverflow = do
let ourMod = mod -- or something more fancy?
cntr <- newCounter (maxInt - (cHUNK_SIZE `div` 2))
spanningCntr <- replicateM cHUNK_SIZE (incrCounter 1 cntr)
-- make sure our test is working
if all (>0) spanningCntr || all (<0) spanningCntr
then error "Sequence meant to span maxBound of counter not actually spanning"
else return ()
let l = map (`ourMod` cHUNK_SIZE) spanningCntr
l' = (dropWhile (/= 0) l) ++ (takeWhile (/= 0) l)
-- (1) test that we overflow the counter without any breaks and our mod function is working properly:
if l' == [0..(cHUNK_SIZE - 1)]
then putStrLn $ "OK"
else error $ "Uh Oh: "++(show l')
-- (2) test that Ints and counter overflow in exactly the same way
let spanningInts = take cHUNK_SIZE $ iterate (+1) (maxInt - (cHUNK_SIZE `div` 2) + 1)
if spanningInts == spanningCntr
then putStrLn "OK"
else do putStrLn $ "Ints overflow differently than counter: "
putStrLn $ "Int: "++(show spanningInts)
putStrLn $ "Counter: "++(show spanningCntr)
error "Fail"
-- We don't use this property
cntr2 <- newCounter maxBound
mbnd <- incrCounter 1 cntr2
if mbnd == minBound
then putStrLn "OK"
else error $ "Incrementing counter at maxbound didn't yield minBound"
-- (3) test subtraction across boundary: count - newFirstIndex, for window spanning boundary.
cntr3 <- newCounter (maxBound - 1)
let ls = take 30 $ iterate (+1) $ maxBound - 10
cs <- mapM (\l-> fmap (subtract l) $ incrCounter 1 cntr3) ls
if cs == replicate 30 10
then putStrLn "OK"
else error $ "Derp. We don't know how subtraction works: "++(show cs)
-- (4) readIORef before fetchAndAdd w/ barriers
-- Test these assumptions:
-- 1) If a CAS fails in thread 1 then another CAS (in thread 2, say) succeeded
-- 2) In the case that thread 1's CAS failed, the ticket returned with (False,tk) will contain that newly-written value from thread 2
testConsistentSuccessFailure = do
var <- newIORef "0"
sem <- newIORef (0::Int)
outs <- replicateM 2 newEmptyMVar
forkSync sem 2 $ test "a" var (outs!!0)
forkSync sem 2 $ test "b" var (outs!!1)
mapM takeMVar outs >>= examine
-- w/r/t (2) above: we only try to find an element read along with False
-- which wasn't sent by another thread, which isn't ideal
where attempts = 100000
test tag var out = do
res <- forM [(1::Int)..attempts] $ \x-> do
let str = (tag++(show x))
tk <- readForCAS var
(b,tk') <- casIORef var tk str
return (if b then str else peekTicket tk' , b)
putMVar out res
examine [res1, res2] = do
-- any failures in either should be marked as successes in the other
let (successes1,failures1) = (\(x,y)-> (Set.fromList $ map fst x, map fst y)) $ partition snd res1
(successes2,failures2) = (\(x,y)-> (Set.fromList $ map fst x, map fst y)) $ partition snd res2
ok1 = all (flip Set.member successes2) failures1
ok2 = all (flip Set.member successes1) failures2
if ok1 && ok2
then if length failures1 < (attempts `div` 6) || length failures2 < (attempts `div` 6)
then error "There was not enough contention to trust test. Please retry."
else putStrLn "OK"
else do print res1
print res2
error "FAILURE!"
-- forkSync :: IORef Int -> Int -> IO a -> IO ThreadId
forkSync sem target io =
forkIO $ (busyWait >> io)
where busyWait =
atomicModifyIORef' sem (\n-> (n+1,())) >> wait
wait = do
n <- readIORef sem
unless (n == target) wait
counterTest = do
n0 <- testAtomicCount newCounter readCounter incrCounter
n1 <- testAtomicCount newMVar takeMVar (\n v-> modifyMVar_ v (evaluate . (+1)) )
if n0 /= n1
then putStrLn $ "Counter broken: expecting "++(show n1)++" got "++(show n0)
else putStrLn "OK"
testAtomicCount new read incr = do
let n = 1000000
procs <- getNumCapabilities
counter <- new (1::Int)
dones <- replicateM procs newEmptyMVar ; starts <- replicateM procs newEmptyMVar
mapM_ (\(start1,done1)-> forkIO $ takeMVar start1 >> replicateM_ (n `div` procs) (incr 1 counter) >> putMVar done1 ()) $ zip starts dones
mapM_ (\v-> putMVar v ()) starts ; mapM_ (\v-> takeMVar v) dones
read counter
-- test ABA issue with these three cases:
-- ()
-- Bool
-- {-# NOINLINE True #-}
-- let true = True
-- returns False
testCAS_ABA_0 = do
a <- newIORef ()
ta <- readForCAS a
atomicModifyIORef' a (\u-> (u,u))
(res, _) <- casIORef a ta ()
print res
{- same
testCAS_ABA_0_nonstrict = do
a <- newIORef ()
ta <- readForCAS a
atomicModifyIORef a (\u-> (u,u))
(res, _) <- casIORef a ta ()
print res
testCAS_ABA_0_u = do
a <- newIORef ()
ta <- readForCAS a
atomicModifyIORef' a (const ((),()))
(res, _) <- casIORef a ta ()
print res
testCAS_ABA_0_sameu = do
let {-# NOINLINE u #-}
u = ()
a <- newIORef u
ta <- readForCAS a
atomicModifyIORef' a (const (u,u))
(res, _) <- casIORef a ta ()
print res
-}
-- returns True
testCAS_ABA_1 = do
a <- newIORef ()
ta <- readForCAS a
modifyIORef a (const ()) -- i.e. readIORef >> writeIORef
(res, _) <- casIORef a ta ()
print res
{- same
testCAS_ABA_1_casMod = do
a <- newIORef ()
ta <- readForCAS a
atomicModifyIORefCAS_ a id
(res, _) <- casIORef a ta ()
print res
testCAS_ABA_1_id = do
a <- newIORef ()
ta <- readForCAS a
modifyIORef a id -- i.e. readIORef >> writeIORef
(res, _) <- casIORef a ta ()
print res
-}
-- returns True
-- ... so the issue isn't re-ordering of readForCas and the read in modifyIORef
-- in fact, no combination of the barriers provided seem to work.
testCAS_ABA_2 = do
a <- newIORef ()
ta <- readForCAS a
loadLoadBarrier
modifyIORef a (const ()) -- i.e. readIORef >> writeIORef
(res, _) <- casIORef a ta ()
print res
testCAS_ABA_3 = do
barrier <- newIORef ()
a <- newIORef ()
ta <- readForCAS a
atomicModifyIORef' barrier (\u-> (u,u)) -- just a barrier
modifyIORef a (const ()) -- i.e. readIORef >> writeIORef
atomicModifyIORef' barrier (\u-> (u,u)) -- just a barrier
(res, _) <- casIORef a ta ()
print res
-- INTERESTING!: /adding/ the modifyIORef /after/ the atomicModifyIORef causes this to return True!
testCAS_ABA_0_modAfterAtomicMod = do
barrier <- newIORef ()
a <- newIORef ()
ta <- readForCAS a
atomicModifyIORef' a (\u-> (u,u))
modifyIORef a (const ()) -- i.e. readIORef >> writeIORef
(res, _) <- casIORef a ta ()
print res
-- ...whereas this one returns False again
testCAS_ABA_0_modBefAtomicMod = do
barrier <- newIORef ()
a <- newIORef ()
ta <- readForCAS a
modifyIORef a (const ()) -- i.e. readIORef >> writeIORef
atomicModifyIORef' a (\u-> (u,u))
(res, _) <- casIORef a ta ()
print res