packages feed

abstract-deque 0.2 → 0.2.2

raw patch · 4 files changed

+294/−101 lines, 4 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.Concurrent.Deque.Tests: numAgents :: Int
+ Data.Concurrent.Deque.Debugger: DebugDeque :: (IORef (Maybe ThreadId), IORef (Maybe ThreadId)) -> (d elt) -> DebugDeque d elt
+ Data.Concurrent.Deque.Debugger: data DebugDeque d elt
+ Data.Concurrent.Deque.Debugger: instance DequeClass d => DequeClass (DebugDeque d)
+ Data.Concurrent.Deque.Debugger: instance PopL d => PopL (DebugDeque d)
+ Data.Concurrent.Deque.Tests: getNumAgents :: IO Int
+ Data.Concurrent.Deque.Tests: setTestThreads :: Int -> Test -> Test
+ Data.Concurrent.Deque.Tests: stdTestHarness :: (IO Test) -> IO ()
- Data.Concurrent.Deque.Tests: test_fifo_OneBottleneck :: DequeClass d => Bool -> Int -> d Int -> IO ()
+ Data.Concurrent.Deque.Tests: test_fifo_OneBottleneck :: DequeClass d => Bool -> Int -> d Elt -> IO ()
- Data.Concurrent.Deque.Tests: test_fifo_filldrain :: DequeClass d => d Int -> IO ()
+ Data.Concurrent.Deque.Tests: test_fifo_filldrain :: DequeClass d => d Elt -> IO ()

Files

+ Data/Concurrent/Deque/Debugger.hs view
@@ -0,0 +1,65 @@+++-- | This module provides a wrapper around a deque that can enforce additional+-- invariants at runtime for debugging purposes.++module Data.Concurrent.Deque.Debugger+       (DebugDeque(DebugDeque))+       where++import Data.IORef+import Control.Concurrent+import Data.Concurrent.Deque.Class++-- newtype DebugDeque d = DebugDeque d++-- | Warning, this enforces the excessively STRONG invariant that if any end of the+-- deque is non-threadsafe then it may ever only be touched by one thread during its+-- entire lifetime.+--+-- This extreme form of monagamy is easier to verify, because we don't have enough+-- information to know if two operations on different threads are racing with one+-- another or are properly synchronized.+--+-- The wrapper data structure has two IORefs to track the last thread that touched+-- the left and right end of the deque, respectively.+data DebugDeque d elt = DebugDeque (IORef (Maybe ThreadId), IORef (Maybe ThreadId)) (d elt) +++instance DequeClass d => DequeClass (DebugDeque d) where +  pushL (DebugDeque (ref,_) q) elt = do+    markThread (leftThreadSafe q) ref+    pushL q elt++  tryPopR (DebugDeque (_,ref) q) = do+    markThread (rightThreadSafe q) ref+    tryPopR q ++  newQ = do l <- newIORef Nothing+            r <- newIORef Nothing+            fmap (DebugDeque (l,r)) newQ++  -- FIXME: What are the threadsafe rules for nullQ?+  nullQ (DebugDeque _ q) = nullQ q+      +  leftThreadSafe  (DebugDeque _ q) = leftThreadSafe q+  rightThreadSafe (DebugDeque _ q) = rightThreadSafe q+++instance PopL d => PopL (DebugDeque d) where +  tryPopL (DebugDeque (ref,_) q) = do+    markThread (leftThreadSafe q) ref+    tryPopL q ++-- | Mark the last thread to use this endpoint.+markThread True _ = return () -- Don't bother tracking.+markThread False ref = do+  last <- readIORef ref+  tid  <- myThreadId+--  putStrLn$"Marking! "++show tid+  atomicModifyIORef ref $ \ x ->+    case x of+      Nothing -> (Just tid, ())+      Just tid2+        | tid == tid2 -> (Just tid,())+        | otherwise   -> error$ "DebugDeque: invariant violated, thread safety not allowed but accessed by: "++show (tid,tid2)
Data/Concurrent/Deque/Tests.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE BangPatterns, RankNTypes #-}+{-# LANGUAGE BangPatterns, RankNTypes, CPP #-}  -- | This module contains a battery of simple tests for queues --   implementing the interface defined in@@ -17,7 +17,13 @@    tests_all,     -- * Testing parameters-   numElems, numAgents, producerRatio+   numElems, getNumAgents, producerRatio,++   -- * Utility for controlling the number of threads used by generated tests.+   setTestThreads,++   -- * Test initialization, reading common configs+   stdTestHarness  )  where  @@ -25,24 +31,40 @@ import qualified Data.Concurrent.Deque.Reference as R  import Control.Monad--- import qualified Data.Vector V--- import qualified Data.Vector.Mutable as MV import Data.Array as A import Data.IORef+import Data.Int+import qualified Data.Set as S -- import System.Mem.StableName import Text.Printf-import GHC.Conc (numCapabilities, throwTo, threadDelay, myThreadId)+import GHC.Conc (throwTo, threadDelay, myThreadId) import Control.Concurrent.MVar import Control.Concurrent (yield, forkOS, forkIO, ThreadId)-import Control.Exception (catch, SomeException, fromException, AsyncException(ThreadKilled))-import System.Environment (getEnvironment)+import Control.Exception (catch, SomeException, fromException, bracket, AsyncException(ThreadKilled))+import System.Environment (withArgs, getArgs, getEnvironment) import System.IO (hPutStrLn, stderr, hFlush, stdout) import System.IO.Unsafe (unsafePerformIO) import System.Random (randomIO, randomRIO)-import Test.HUnit+import qualified Test.Framework as TF+import Test.Framework.Providers.HUnit  (hUnitTestToTests)+import Test.HUnit as HU  import Debug.Trace (trace) +#if __GLASGOW_HASKELL__ >= 704+import GHC.Conc (getNumCapabilities, setNumCapabilities, getNumProcessors)+#else+import GHC.Conc (numCapabilities)+getNumCapabilities :: IO Int+getNumCapabilities = return numCapabilities++setNumCapabilities :: Int -> IO ()+setNumCapabilities = error "setNumCapabilities not supported in this older GHC!  Set NUMTHREADS and +RTS -N to match."++getNumProcessors :: IO Int+getNumProcessors = return 1 +#endif    + theEnv :: [(String, String)] theEnv = unsafePerformIO getEnvironment @@ -71,11 +93,11 @@  -- | How many communicating agents are there?  By default one per -- thread used by the RTS.-numAgents :: Int-numAgents = case lookup "NUMAGENTS" theEnv of -             Nothing  -> numCapabilities-             Just str -> warnUsing ("NUMAGENTS = "++str) $ -                         read str+getNumAgents :: IO Int+getNumAgents = case lookup "NUMAGENTS" theEnv of +                Nothing  -> getNumCapabilities+                Just str -> warnUsing ("NUMAGENTS = "++str) $ +                            return (read str)  -- | It is possible to have imbalanced concurrency where there is more -- contention on the producing or consuming side (which corresponds to@@ -90,19 +112,75 @@ warnUsing str a = trace ("  [Warning]: Using environment variable "++str) a  +-- | Dig through the test constructors to find the leaf IO actions and bracket them+--   with a thread-setting action.+setTestThreads :: Int -> HU.Test -> HU.Test+setTestThreads nm tst = loop False tst+ where+   loop flg x = +    case x of+      TestLabel lb t2 -> TestLabel (decor flg lb) (loop True t2)+      TestList ls -> TestList (map (loop flg) ls)+      TestCase io -> TestCase (bracketThreads nm io) +   -- We only need to insert the numcapabilities in the description string ONCE:+   decor False lb = "N"++show nm++"_"++ lb+   decor True  lb = lb++   bracketThreads :: Int -> IO a -> IO a+   bracketThreads n act =+     bracket (getNumCapabilities)+             setNumCapabilities+             (\_ -> do dbgPrint 1 ("\n   [Setting # capabilities to "++show n++" before test] \n")+                       setNumCapabilities n+                       act)++stdTestHarness :: (IO Test) -> IO ()+stdTestHarness genTests = do +  numAgents <- getNumAgents +  putStrLn$ "Running with numElems "++show numElems++" and numAgents "++ show numAgents+  putStrLn "Use NUMELEMS, NUMAGENTS, NUMTHREADS to control the size of this benchmark."+  args <- getArgs++  np <- getNumProcessors+  putStrLn $"Running on a machine with "++show np++" hardware threads."++  -- We allow the user to set this directly, because the "-t" based regexp selection+  -- of benchmarks is quite limited.+  let all_threads = case lookup "NUMTHREADS" theEnv of+                      Just str -> [read str]+                      Nothing -> S.toList$ S.fromList$+                        [1, 2, np `quot` 2, np, 2*np ]+  putStrLn $"Running tests for these thread settings: "  ++show all_threads+  all_tests <- genTests ++  -- Don't allow concurent tests (the tests are concurrent!):+  withArgs (args ++ ["-j1","--jxml=test-results.xml"]) $ do ++    -- Hack, this shouldn't be necessary, but I'm having problems with -t:+    tests <- case all_threads of+              [one] -> do cap <- getNumCapabilities+                          unless (cap == one) $ setNumCapabilities one+                          return all_tests+              _ -> return$ TestList [ setTestThreads n all_tests | n <- all_threads ]+    TF.defaultMain$ hUnitTestToTests tests++ ---------------------------------------------------------------------------------------------------- -- Test a plain FIFO queue: ---------------------------------------------------------------------------------------------------- +-- The type of data that we push over the queues.+type Elt = Int64+ -- | This test serially fills up a queue and then drains it.-test_fifo_filldrain :: DequeClass d => d Int -> IO ()+test_fifo_filldrain :: DequeClass d => d Elt -> IO () test_fifo_filldrain q =    do      dbgPrintLn 1 "\nTest FIFO queue: sequential fill and then drain"      dbgPrintLn 1 "===============================================" --     let n = 1000-     let n = numElems+     let n = fromIntegral numElems      dbgPrintLn 1$ "Done creating queue.  Pushing "++show n++" elements:"      forM_ [1..n] $ \i -> do         pushL q i@@ -115,7 +193,7 @@            when (i < 200) $ dbgPrint 1 $ printf " %d" x            loop (i-1) (sumR + x)      s <- loop n 0-     let expected = sum [1..n] :: Int+     let expected = sum [1..n] :: Elt      dbgPrint 1 $ printf "\nSum of popped vals: %d should be %d\n" s expected      when (s /= expected) (assertFailure "Incorrect sum!")      return ()@@ -126,7 +204,7 @@ -- thread performs its designated operation as fast as possible.  The -- 'Int' argument 'total' designates how many total items should be -- communicated (irrespective of 'numAgents').-test_fifo_OneBottleneck :: DequeClass d => Bool -> Int -> d Int -> IO ()+test_fifo_OneBottleneck :: DequeClass d => Bool -> Int -> d Elt -> IO () test_fifo_OneBottleneck doBackoff total q =    do      assertBool "test_fifo_OneBottleneck requires thread safe left end"  (leftThreadSafe q)@@ -138,12 +216,15 @@       bl <- nullQ q      dbgPrintLn 1$ "Check that queue is initially null: "++show bl++     numAgents <- getNumAgents            let producers = max 1 (round$ producerRatio * (fromIntegral numAgents) / (producerRatio + 1)) 	 consumers = max 1 (numAgents - producers) 	 perthread  = total `quot` producers          (perthread2,remain) = total `quotRem` consumers-     -     when (not doBackoff && (numCapabilities == 1 || numCapabilities < producers + consumers)) $ ++     numCap <- getNumCapabilities     +     when (not doBackoff && (numCap == 1 || numCap < producers + consumers)) $         error$ "The aggressively busy-waiting version of the test can only run with the right thread settings."            dbgPrint 1 $ printf "Forking %d producer threads, each producing %d elements.\n" producers perthread@@ -153,9 +234,9 @@  	myfork "producer thread" $ do           let start = ind * perthread            dbgPrint 1 $ printf "  * Producer thread %d pushing ints from %d to %d \n" ind start (start+perthread - 1)-          for_ start (start+perthread) $ \ i -> do +          forI_ start (start+perthread) $ \ i -> do  	     pushL q i-             when (i < start + 10) $ dbgPrint 1 $ printf " [p%d] pushed %d \n" ind i+             -- when (i < start + 10) $ dbgPrint 1 $ printf " [p%d] pushed %d \n" ind i       ls <- forkJoin consumers $ \ ind ->                    let mymax = if ind==0 then perthread2 + remain else perthread2@@ -170,28 +251,30 @@      let finalSum = Prelude.sum (map fst ls)      dbgPrintLn 1$ "Consumers DONE.  Maximum retries for each consumer thread: "++ show (map snd ls)      dbgPrintLn 1$ "Final sum: "++ show finalSum-     assertEqual "Correct final sum" (expectedSum (producers * perthread)) finalSum+     assertEqual "Correct final sum" (expectedSum (fromIntegral$ producers * perthread)) finalSum      dbgPrintLn 1$ "Checking that queue is finally null..."      b <- nullQ q      if b then dbgPrintLn 1$ "Sum matched expected, test passed."           else assertFailure "Queue was not empty!!" --- | This test uses a separate queue per consumer thread.  The queues--- are used in a single-writer multiple-reader fashion (mailboxes).-test_contention_free_parallel :: DequeClass d => Bool -> Int -> IO (d Int) -> IO ()+-- | This test uses a separate queue per producer/consumer pair.  The queues+-- are used in a single-writer single-reader.+test_contention_free_parallel :: DequeClass d => Bool -> Int -> IO (d Elt) -> IO () test_contention_free_parallel doBackoff total newqueue =    do       dbgPrintLn 1$ "\nTest FIFO queue: producers & consumers thru N queues"                ++(if doBackoff then " (with backoff)" else "(hard busy wait)")      dbgPrintLn 1 "======================================================"             mv <- newEmptyMVar-          ++     numAgents <- getNumAgents      let producers = max 1 (round$ producerRatio * (fromIntegral numAgents) / (producerRatio + 1)) 	 consumers = producers -- Must be matched 	 perthread  = total `quot` producers      qs <- sequence (replicate consumers newqueue)-     -     when (not doBackoff && (numCapabilities == 1 || numCapabilities < producers + consumers)) $ ++     numCap <- getNumCapabilities +     when (not doBackoff && (numCap == 1 || numCap < producers + consumers)) $         error$ "The aggressively busy-waiting version of the test can only run with the right thread settings."            dbgPrint 1 $ printf "Forking %d producer threads, each producing %d elements.\n" producers perthread@@ -200,9 +283,9 @@      forM_ (zip [0..producers-1] qs) $ \ (id, q) ->   	myfork "producer thread" $           let start = id*perthread in-          for_ 0 perthread $ \ i -> do +          forI_ 0 perthread $ \ i -> do  	     pushL q i-             when (i - id*producers < 10) $ dbgPrint 1 $ printf " [%d] pushed %d \n" id i+             -- when (i - id*producers < 10) $ dbgPrint 1 $ printf " [%d] pushed %d \n" id i       forM_ (zip [0..consumers-1] qs) $ \ (id, q) ->   	myfork "consumer thread" $ do @@ -211,7 +294,8 @@                 (x,iters) <- if doBackoff then spinPopBkoff q                                            else spinPopHard  q                 when (i < 10) $ dbgPrint 1 $ printf " [%d] popped #%d = %d \n" id i x-                unless (x == i) $ error $ "Message out of order! Expected "++show i++" recevied "++show x+                unless (x == fromIntegral i) $+                  error $ "Message out of order! Expected "++show i++" recevied "++show x                 consume_loop (sum+x) (max maxiters iters) (i+1)           pr <- consume_loop 0 0 0 	  putMVar mv pr@@ -221,7 +305,7 @@      let finalSum = Prelude.sum (map fst ls)      dbgPrintLn 1$ "Consumers DONE.  Maximum retries for each consumer thread: "++ show (map snd ls)      dbgPrintLn 1$ "All messages received in order.  Final sum: "++ show finalSum-     assertEqual "Correct final sum" (producers * expectedSum perthread) finalSum          +     assertEqual "Correct final sum" (fromIntegral producers * expectedSum (fromIntegral perthread)) finalSum                dbgPrintLn 1$ "Checking that queue is finally null..."      bs <- mapM nullQ qs      if all id bs@@ -232,9 +316,10 @@  -- | This test uses a number of producer and consumer threads which push and pop -- elements from random positions in an array of FIFOs.-test_random_array_comm :: DequeClass d => Int -> Int -> IO (d Int) -> IO ()-test_random_array_comm size total newqueue | size > 0 = do-+test_random_array_comm :: DequeClass d => Int -> Int -> IO (d Elt) -> IO ()+test_random_array_comm size total newqueue = do+   assertBool "positive size" (size > 0)+      qs <- sequence (replicate size newqueue) --   arr <- V.thaw $ V.fromlist qs    let arr = A.listArray (0,size-1) qs@@ -245,9 +330,10 @@    dbgPrintLn 1$ "\nTest FIFO queue: producers & consumers select random queues"    dbgPrintLn 1 "======================================================"        +   numAgents <- getNumAgents    let producers = max 1 (round$ producerRatio * (fromIntegral numAgents) / (producerRatio + 1))        consumers = max 1 (numAgents - producers)-       perthread  = total `quot` producers+       perthread           = fromIntegral (total `quot` producers)        (perthread2,remain) = total `quotRem` consumers        dbgPrintLn 1 $ printf "Forking %d producer threads, each producing %d elements.\n" producers perthread@@ -257,9 +343,9 @@       myfork "producer thread" $         for_ 0 perthread $ \ i -> do            -- Randomly pick a position:-           ix <- randomRIO (0,size-1) :: IO Int+           ix <- randomRIO (0,size - 1) :: IO Int            pushL (arr ! ix) i-           when (i - ind*producers < 10) $ dbgPrint 1 $ printf " [%d] pushed %d \n" ind i+           -- when (i - ind*producers < 10) $ dbgPrint 1 $ printf " [%d] pushed %d \n" ind i     -- Each consumer doesn't quit until it has popped "perthread":    sums <- forkJoin consumers $ \ ind ->@@ -282,7 +368,7 @@    let finalSum = Prelude.sum sums    dbgPrintLn 1$ "Final sum: "++ show finalSum ++ ", per-consumer sums: "++show sums    dbgPrintLn 1$ "Checking that queue is finally null..."-   assertEqual "Correct final sum" (producers * expectedSum perthread) finalSum+   assertEqual "Correct final sum" (fromIntegral producers * expectedSum perthread) finalSum    bs <- mapM nullQ qs    if all id bs      then dbgPrintLn 1$ "Sum matched expected, test passed."@@ -298,7 +384,7 @@ -- | This creates an HUnit test list to perform all the tests that apply to a --   single-ended (threadsafe) queue.  It requires thread safety at /both/ ends. tests_fifo :: DequeClass d => (forall elt. IO (d elt)) -> Test-tests_fifo newq = TestList $+tests_fifo newq = TestLabel "single-ended-queue-tests"$ TestList $   tests_basic newq ++    tests_fifo_exclusive newq   @@ -319,7 +405,7 @@ tests_basic :: DequeClass d => (forall elt. IO (d elt)) -> [Test] tests_basic newq =   [ TestLabel "test_fifo_filldrain"  (TestCase$ assert $ newq >>= test_fifo_filldrain)-  , TestLabel "test_contention_free_parallel" (TestCase$ assert $ test_contention_free_parallel True numElems newq)    +  , TestLabel "test_contention_free_parallel" (TestCase$ assert $ test_contention_free_parallel True numElems newq)   ]  ----------------------------------------------------------------------------------------------------@@ -347,18 +433,24 @@     [Just "one",Just "two",Just "four",Just "three",Nothing,Nothing]  --- | This test uses a number of producer and consumer threads which push and pop--- elements from random positions in an array of FIFOs.-test_random_work_stealing :: (DequeClass d, PopL d) => Int -> IO (d Int) -> IO ()++-- | This test uses a number of worker threads which randomly either push or pop work+-- to their local work stealing deque.  Also, there are consumer (always thief)+-- threads which never produce and only consume.+test_random_work_stealing :: (DequeClass d, PopL d) => Int -> IO (d Elt) -> IO () test_random_work_stealing total newqueue = do        dbgPrintLn 1$ "\nTest FIFO queue: producers & consumers select random queues"    dbgPrintLn 1 "======================================================"        -   let producers = max 1 (round$ producerRatio * (fromIntegral numAgents) / (producerRatio + 1))-       consumers = max 1 (numAgents - producers)-       perthread  = total `quot` producers-       (perthread2,remain) = total `quotRem` consumers+   numAgents <- getNumAgents+   let producers, consumers :: Int +       perthread, realtotal, perthread2 :: Elt+       producers = max 1 (round$ producerRatio * (fromIntegral numAgents) / (producerRatio + 1))+       consumers = max 1 (fromIntegral numAgents - producers)+       perthread = fromIntegral total `quot` fromIntegral producers+       realtotal = perthread * fromIntegral producers+       (perthread2,remain) = realtotal `quotRem` fromIntegral consumers            qs <- sequence (replicate producers newqueue)       -- A work-stealing deque only has ONE threadsafe end:@@ -366,58 +458,72 @@    let arr = A.listArray (0,producers - 1) qs        dbgPrint 1 $ printf "Forking %d producer threads, each producing %d elements.\n" producers perthread-   dbgPrint 1 $ printf "Forking %d consumer threads, each consuming %d elements.\n" consumers perthread2+   dbgPrint 1 $ printf "Forking %d consumer threads, each consuming ~%d elements.\n" consumers perthread2        prod_results <- newEmptyMVar    forM_ (zip [0..producers-1] qs) $ \ (ind,myQ) -> do        myfork "producer thread" $-        let loop i !acc | i == perthread = putMVar prod_results acc-            loop i !acc = -              do -- Randomly push or pop:+        let loop :: Elt -> Elt -> Elt -> IO ()+            loop i !pops !acc +              | i == perthread = putMVar prod_results (pops,acc)+              | otherwise = do +                 -- Randomly push or pop:                  b   <-  randomIO  :: IO Bool                  if b then do                    x <- spinPopN 100 (tryPopL myQ)+                   -- In the case where we pop, we do NOT advance 'i', saving that+                   -- for the next iteration that acutally does a push.                    case x of-                     Nothing -> loop i acc-                     Just n  -> loop i (n+acc)+                     Nothing -> loop i  pops    acc+                     Just n  -> loop i (pops+1) (n+acc)                    else do                    pushL myQ i-                   when (i - ind*producers < 10) $ dbgPrint 1 $ printf " [%d] pushed %d \n" ind i-                   loop (i+1) acc-        in loop 0 0+                   -- when (i - ind*producers < 10) $ dbgPrint 1 $ printf " [%d] pushed %d \n" ind i+                   loop (i+1) pops acc+        in loop 0 0 0 -   -- Each consumer doesn't quit until it has popped "perthread":+   -- In this version    consumer_sums <- forkJoin consumers $ \ ind ->-        let mymax = if ind==0 then perthread2 + remain else perthread2     -            consume_loop summ  i | i == mymax = return summ-            consume_loop !summ i = do+        let mymax = if ind==0 then perthread2 + remain else perthread2             +            consume_loop !summ !successes i +               | i == mymax = return (successes, summ)+               | otherwise  = do                -- Randomly pick a position:               ix <- randomRIO (0,producers - 1) :: IO Int               -- Try to pop something, but not too hard:               m <- spinPopN 100 (tryPopR (arr ! ix))               case m of                 Just x -> do-                  when (i < 10) $ dbgPrint 1 $ printf " [%d] popped #%d = %d\n" ind i x-                  consume_loop (summ+x) (i+1)+                  when (i < 10) $ dbgPrint 1 $ printf " [%d] popped try#%d = %d\n" ind i x+                  consume_loop (summ+x) (successes+1) (i+1)                 Nothing ->-                  consume_loop summ (i+1) -- Increment even if we don't get a result.-        in consume_loop 0 0+                  consume_loop summ successes (i+1) -- Increment even if we don't get a result.+        in do dbgPrintLn 1 ("   Beginning consumer thread loop for "++show mymax ++" attempts.")+              consume_loop 0 0 0+   -- <-- Consumers are finished as of here.     dbgPrintLn 1  "Reading sums from MVar..."    prod_ls <- mapM (\_ -> takeMVar prod_results) [1..producers] +   -- We sequentially read out the leftovers after all the parallel tasks are done:    leftovers <- forM qs $ \ q ->-     let loop !acc = do-           x <- tryPopR q+     let loop !cnt !acc = do+           x <- tryPopR q -- This should work as a popR OR a popL.            case x of-             Nothing -> return acc-             Just n  -> loop (acc+n)-     in loop 0+             Nothing -> return (cnt,acc)+             Just n  -> loop (cnt+1) (acc+n)+     in loop 0 0         -   let finalSum = Prelude.sum (consumer_sums ++ prod_ls ++ leftovers)-   dbgPrintLn 1$ "Final sum: "++ show finalSum ++ ", producer/consumer/leftover sums: "++show (prod_ls, consumer_sums, leftovers)+   let finalSum = Prelude.sum (map snd consumer_sums ++ +                               map snd prod_ls ++ +                               map snd leftovers)+   dbgPrintLn 0$ "Final sum: "++ show finalSum ++ ", producer/consumer/leftover sums: "++show (prod_ls, consumer_sums, leftovers)+   dbgPrintLn 1$ "Total pop events: "++ show (Prelude.sum (map fst consumer_sums ++ +                                                           map fst prod_ls ++ +                                                           map fst leftovers))+                 ++" should be "++ (show$ realtotal)    dbgPrintLn 1$ "Checking that queue is finally null..."-   assertEqual "Correct final sum" (producers * expectedSum perthread) finalSum+   assertEqual "Correct final sum" (fromIntegral producers * expectedSum perthread) finalSum    bs <- mapM nullQ qs    if all id bs      then dbgPrintLn 1$ "Sum matched expected, test passed."@@ -428,9 +534,9 @@ -- | Aggregate tests for work stealing queues.  None of these require thread-safety -- on the left end.  There is some duplication with tests_fifo. tests_wsqueue :: (PopL d) => (forall elt. IO (d elt)) -> Test-tests_wsqueue newq = TestList $- tests_basic newq ++- tests_wsqueue_exclusive newq+tests_wsqueue newq = TestLabel "work-stealing-deque-tests"$ TestList $+ tests_wsqueue_exclusive newq +++ tests_basic newq   -- Internal: factoring this out. tests_wsqueue_exclusive :: (PopL d) => (forall elt. IO (d elt)) -> [Test]@@ -446,7 +552,7 @@  -- | This requires double ended queues that are threadsafe on BOTH ends. tests_all :: (PopL d) => (forall elt. IO (d elt)) -> Test-tests_all newq = TestList $ +tests_all newq = TestLabel "full-deque-tests"$ TestList $    tests_basic newq ++   tests_fifo_exclusive newq ++   tests_wsqueue_exclusive newq @@ -487,6 +593,7 @@      ls <- mapM readMVar answers      return ls +-- | Pop from the right end more gently. spinPopBkoff :: DequeClass d => d t -> IO (t, Int) spinPopBkoff q = loop 1  where @@ -505,18 +612,20 @@        Nothing -> do                       -- Every `sleepevery` times do a significant delay: 		     if n `mod` sleepevery == 0 -		      then do putStr "!"+		      then do dbgPrint 1 "!"                               threadDelay n -- 1ms after 1K fails, 2 after 2K... 		      else when (n > hardspinfor) $ do -                             putStr "."+                             dbgPrint 1 "."                              hFlush stdout 			     yield -- At LEAST yield... you'd think this is pretty strong backoff. 		     loop (n+1)        Just x  -> return (x, n)  --- | This is always a crazy and dangerous thing to do -- GHC cannot---   deschedule this spinning thread because it does not allocate:+-- | This is always a crazy and dangerous thing to do -- GHC cannot deschedule this+-- spinning thread because it does not allocate.  Thus one can run into a deadlock+-- situation where the consumer holds the capabilitity hostage, not letting the+-- producer run (and therefore unblock it). spinPopHard :: (DequeClass d) => d t -> IO (t, Int) spinPopHard q = loop 1  where @@ -537,19 +646,21 @@ -- My own forM for numeric ranges (not requiring deforestation optimizations). -- Inclusive start, exclusive end. {-# INLINE for_ #-}-for_ :: Monad m => Int -> Int -> (Int -> m ()) -> m ()+for_ :: Monad m => Elt -> Elt -> (Elt -> m ()) -> m () for_ start end _fn | start > end = error "for_: start is greater than end" for_ start end fn = loop start   where    loop !i | i == end  = return () 	   | otherwise = do fn i; loop (i+1) +forI_ :: Monad m => Int -> Int -> (Elt -> m ()) -> m ()+forI_ st en = for_ (fromIntegral st) (fromIntegral en)  ---------------------------------------------------------------------------------------------------- -- DEBUGGING ---------------------------------------------------------------------------------------------------- --- | Debugging flag shared by all accelerate-backend-kit modules.+-- | Debugging flag shared by several modules. --   This is activated by setting the environment variable DEBUG=1..5 dbg :: Int dbg = case lookup "DEBUG" theEnv of
Test.hs view
@@ -9,20 +9,24 @@  import Test.Framework (defaultMain) import Test.Framework.Providers.HUnit     (hUnitTestToTests)-import Test.HUnit (assert, assertEqual, Test(TestCase, TestList))+import Test.HUnit (assert, assertEqual, Test(TestCase, TestList, TestLabel))  import qualified Data.Concurrent.Deque.Tests as T import qualified Data.Concurrent.Deque.Reference as R+import qualified Data.Concurrent.Deque.Class as C+import Data.Concurrent.Deque.Debugger (DebugDeque)+import System.Environment (withArgs)  -- Import the instances:-import qualified Data.Concurrent.Deque.Reference.DequeInstance -import System.Exit+import Data.Concurrent.Deque.Reference.DequeInstance () +test_1 :: Test test_1 = TestCase $ assert $    do q <- R.newQ -- Select a specific implementation.      pushR q 3      Just x <- tryPopR q-     assertEqual "test_1 result" x 3+     assertEqual "test_1 result" x (3::Integer) +test_2 :: Test test_2 = TestCase $ assert $    do       -- Here's an example of type-based restriction of the queue implementation:@@ -32,16 +36,26 @@      Just x <- tryPopR q      assertEqual "test_2 result" x 33 +main :: IO () #if __GLASGOW_HASKELL__ >= 700-main = do -  putStrLn "[ Test executable: test reference deque implementation... ]"-  defaultMain$ hUnitTestToTests$ -      TestList $ -        [ -          T.tests_all R.newQ -        , test_1-	, test_2-        ]+main = T.stdTestHarness $ return all_tests+ where + all_tests :: Test+ all_tests = +   TestLabel "Reference_Deque" $ TestList $ +     [ TestLabel "test_1" test_1+     , TestLabel "test_2" test_2 +     , TestLabel "direct"$ T.tests_all R.newQ+     -- Test going through the class interface as well:  +     , TestLabel "thru_class"$ T.tests_all (C.newQ :: IO (R.SimpleDeque a))+     , TestLabel "with_debug"$ T.tests_all (C.newQ :: IO (DebugDeque R.SimpleDeque a))+     ]++-- main = do +--   putStrLn "[ Test executable: test reference deque implementation... ]"+--   withArgs ["-j1","--jxml=test-results.xml"] $   +--     defaultMain$ hUnitTestToTests$+  #else main = putStrLn "WARNING: Tests disabled for GHC < 7" #endif
abstract-deque.cabal view
@@ -1,5 +1,5 @@ Name:                abstract-deque-Version:             0.2+Version:             0.2.2 License:             BSD3 License-file:        LICENSE Author:              Ryan R. Newton@@ -20,6 +20,7 @@ -- 0.1.6: Make useCAS default FALSE. -- 0.1.7: Add leftThreadSafe / rightThreadSafe -- 0.2:   [breaking] Refactor names of exposed Tests+-- 0.2.2: Add Debugger  Synopsis: Abstract, parameterized interface to mutable Deques. @@ -55,10 +56,11 @@   exposed-modules:   Data.Concurrent.Deque.Class,                      Data.Concurrent.Deque.Tests,                      Data.Concurrent.Deque.Reference,-                     Data.Concurrent.Deque.Reference.DequeInstance+                     Data.Concurrent.Deque.Reference.DequeInstance,+                     Data.Concurrent.Deque.Debugger   build-depends:     base >= 4 && < 5, array, random,-                     containers, HUnit-+                     containers, +                     HUnit, test-framework >= 0.6, test-framework-hunit >= 0.2.7   if flag(useCAS) && impl( ghc >= 7.4 ) && !os(mingw32) {     build-depends:   IORefCAS >= 0.2      cpp-options:  -DUSE_CAS -DDEFAULT_SIGNATURES@@ -77,8 +79,9 @@     type:       exitcode-stdio-1.0     main-is:    Test.hs     build-depends:  base >= 4 && < 5, containers, HUnit, array, random,-                    test-framework >= 0.6, test-framework-hunit >= 0.2.7+                    HUnit, test-framework >= 0.6, test-framework-hunit >= 0.2.7     ghc-options: -O2 -threaded -rtsopts+     -- ghc-options: -keep-tmp-files -dsuppress-module-prefixes -ddump-to-file -ddump-core-stats -ddump-simpl-stats -dcore-lint -dcmm-lint     -- ghc-options: -ddump-ds -ddump-simpl -ddump-stg -ddump-asm -ddump-bcos -ddump-cmm -ddump-opt-cmm -ddump-inlinings