packages feed

next-ref (empty) → 0.1.0.0

raw patch · 5 files changed

+233/−0 lines, 5 filesdep +basedep +hspecdep +next-refsetup-changed

Dependencies added: base, hspec, next-ref, stm

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright skedge.me (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ next-ref.cabal view
@@ -0,0 +1,33 @@+name: next-ref+version: 0.1.0.0+cabal-version: >=1.10+build-type: Simple+license: BSD3+license-file: LICENSE+copyright: 2016 skedge.me+maintainer: jonathangfischoff@gmail.com+synopsis: A concurrency primitive for a slow consumer.+description:+    A concurrency primitive for a slow consumer that can tolerate missing some updates.+category: Web+author: Jonathan Fischoff++library+    exposed-modules:+        Control.Concurrent.NextRef+    build-depends:+        base >=4.7 && <5,+        stm >=2.4.4.1 && <2.5+    default-language: Haskell2010+    hs-source-dirs: src++test-suite next-ref-test+    type: exitcode-stdio-1.0+    main-is: Spec.hs+    build-depends:+        base >=4.8.2.0 && <4.9,+        next-ref >=0.1.0.0 && <0.2,+        hspec >=2.2.3 && <2.3+    default-language: Haskell2010+    hs-source-dirs: test+    ghc-options: -threaded -rtsopts -with-rtsopts=-N
+ src/Control/Concurrent/NextRef.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE LambdaCase, RecordWildCards, BangPatterns #-}+{-| This package contains a concurrency primitive which can be used to limit an+    expensive consumer from running unnecessarily. Crucially the consumer must+    be able to tolerate missing some updates.++    'NextRef' provides non-blocking writes, blocking reads, and non-blocking +    reads.++    The blocking read interface ('takeNextRef') will not necessarily present +    all values. ++    Additionally the 'NextRef' can be 'closed'. This is useful to graceful+    shutdown the consumer when the producer closes the 'NextRef'+    +-}+module Control.Concurrent.NextRef +  ( NextRef +  , newNextRef+  , takeNextRef+  , readLast+  , writeNextRef+  , modifyNextRef +  , close+  , open+  , status+  , Status (..)+  ) where+import Control.Concurrent.STM+import Data.IORef++-- | Status is used to prevent future reads. When the status is 'Closed'+--   'takeNextRef' will always return 'Nothing'. When the status is +--   open it will return Just. This is based off of the design of 'TMQueue'+--   from the 'stm-chans' package+data Status = Open | Closed+  deriving (Show, Eq, Ord, Read, Enum, Bounded)++-- | A concurrency primitive for a slow consumer that can tolerate+--   missing some updates.+data NextRef a = NextRef  +  { nrAccum     :: IORef a+  , nrNextValue :: TMVar a+  , nrStatus    :: TVar  Status +  }++-- | Create a 'NextVar'+newNextRef :: a -> IO (NextRef a)+newNextRef x = NextRef <$> newIORef x <*> newTMVarIO x <*> newTVarIO Open++-- | Block until the next value is available. If the 'NextVar' is +--   closed it returns 'Nothing' immediantly. +takeNextRef :: NextRef a -> IO (Maybe a)+takeNextRef NextRef {..} = atomically $ readTVar nrStatus >>= \case+  Closed -> return Nothing+  Open   -> Just <$> takeTMVar nrNextValue++update :: NextRef a -> a -> IO ()+update NextRef {..} !new = atomically $ readTVar nrStatus >>= \case+  Closed -> return ()+  Open   -> do+    tryTakeTMVar nrNextValue+    putTMVar     nrNextValue new++-- | Read the most recent value. Non-blocking+readLast :: NextRef a -> IO a+readLast NextRef {..} = readIORef nrAccum++tupleResult :: (a, b) -> (a, (a, b))+tupleResult (x, y) = (x, (x, y))++-- | Write a new value. Never blocks.+writeNextRef :: NextRef a -> a -> IO ()+writeNextRef nv@(NextRef {..}) newValue = do +  writeIORef nrAccum newValue+  update nv newValue++-- | Apply a function to current value to produce the next value and return +--   a result. +modifyNextRef :: NextRef a -> (a -> (a, b)) -> IO b+modifyNextRef nv@(NextRef {..}) f = do+  (!newValue, !result) <- atomicModifyIORef' nrAccum $ tupleResult . f+  update nv newValue+  return result++-- | Modify the status of the 'NextRef' to 'Closed'. All future reads+--   using 'takeNextRef' will result a 'Nothing'. 'readLast' is unaffected.+close :: NextRef a -> IO ()+close NextRef {..} = atomically $ writeTVar nrStatus Closed++-- | Modify the status of the 'NextRef' to 'Closed'. All future reads+--   using 'takeNextRef' will return a 'Just'. 'readLast' is unaffected.+open :: NextRef a -> IO ()+open NextRef {..} = atomically $ writeTVar nrStatus Open++-- | Get the current status of the 'NextRef'+status :: NextRef a -> IO Status+status = atomically . readTVar . nrStatus
+ test/Spec.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE LambdaCase #-}+import Test.Hspec+import Control.Concurrent.NextRef+import GHC.Conc+import Control.Monad (void, replicateM_)++while :: IO Bool -> IO () -> IO ()+while test act = test >>= \case +  True  -> act >> while test act+  False -> return ()+  ++main :: IO ()+main = hspec $ describe "NextRef" $ do +  it "newNextRef/takeNext/takeNext blocks" $ do+    ref <- newNextRef ()+    takeNextRef ref+    threadId <- forkIO $ void $ takeNextRef ref+    +    while ((== ThreadRunning) `fmap` threadStatus threadId) $ threadDelay 100000+    +    stat <- threadStatus threadId +    stat `shouldBe` ThreadBlocked BlockedOnSTM+    +  it "writing never blocks" $ do+    ref <- newNextRef 1+    replicateM_ 10 $ writeNextRef ref 2+    True `shouldBe` True +  +  it "readLast does not block" $ do+    ref <- newNextRef ()+    takeNextRef ref+    readLast ref+    +    True `shouldBe` True +    +  it "takeNext/write/takeNext doesn't block, updates correctly" $ do+    ref <- newNextRef (1 :: Int)+    actual <- takeNextRef ref+    actual `shouldBe` Just 1+    +    writeNextRef ref 2+    +    actual1 <- takeNextRef ref+    actual1 `shouldBe` Just 2+    +  it "takeNext/modify/takeNext doesn't block, updates correctly" $ do+    ref <- newNextRef (1 :: Int)+    actual <- takeNextRef ref+    actual `shouldBe` Just 1+    +    modifyNextRef ref (\x -> (x+1, ()))+    +    actual1 <- takeNextRef ref+    actual1 `shouldBe` Just 2+    +  it "close/takeNext give nothing" $ do+    ref <- newNextRef ()+    close ref+    actual <- takeNextRef ref+    actual `shouldBe` Nothing+    +  it "close/open/takeNext gives value" $ do+    ref <- newNextRef ()+    close ref+    open  ref+    actual <- takeNextRef ref+    actual `shouldBe` Just ()+  +    +