zoovisitor 0.1.4.0 → 0.1.5.0
raw patch · 5 files changed
+122/−8 lines, 5 filesdep +asyncPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: async
API changes (from Hackage documentation)
- ZooKeeper.Recipe.Election: election :: ZHandle -> CBytes -> CBytes -> IO () -> (DataCompletion -> IO ()) -> IO ()
+ ZooKeeper.Recipe: election :: ZHandle -> CBytes -> CBytes -> IO () -> (DataCompletion -> IO ()) -> IO ()
+ ZooKeeper.Recipe: lock :: ZHandle -> CBytes -> CBytes -> IO CBytes
+ ZooKeeper.Recipe: unlock :: ZHandle -> CBytes -> IO ()
Files
- src/ZooKeeper/Recipe.hs +7/−0
- src/ZooKeeper/Recipe/Election.hs +3/−1
- src/ZooKeeper/Recipe/Lock.hs +62/−0
- test/Spec.hs +45/−5
- zoovisitor.cabal +5/−2
+ src/ZooKeeper/Recipe.hs view
@@ -0,0 +1,7 @@+module ZooKeeper.Recipe+ ( module ZooKeeper.Recipe.Election+ , module ZooKeeper.Recipe.Lock+ ) where++import ZooKeeper.Recipe.Election+import ZooKeeper.Recipe.Lock
src/ZooKeeper/Recipe/Election.hs view
@@ -1,4 +1,6 @@-module ZooKeeper.Recipe.Election where+module ZooKeeper.Recipe.Election+ ( election+ ) where import Control.Monad import qualified Z.Data.Builder as B
+ src/ZooKeeper/Recipe/Lock.hs view
@@ -0,0 +1,62 @@+module ZooKeeper.Recipe.Lock+ ( lock+ , unlock+ ) where++import Control.Exception+import Control.Monad+import Z.Data.CBytes (CBytes)++import ZooKeeper+import ZooKeeper.Exception+import ZooKeeper.Recipe.Utils (SequenceNumWithGUID (..),+ createSeqEphemeralZNode,+ mkSequenceNumWithGUID)+import ZooKeeper.Types++-- | To acquire a distributed lock.+lock :: ZHandle+ -- ^ The zookeeper handle obtained by a call to 'zookeeperResInit'+ -> CBytes+ -- ^ The path to get the lock. Ephemeral znodes will be put on it+ -> CBytes+ -- ^ The GUID for this zookeeper session. To handle recoverable execptions+ -- correctly, it should be distinct from different sessions+ -> IO CBytes+ -- ^ The real path of the lock that acquired. It will be used when unlocking+ -- the same lock+lock zk lockPath guid = do+ -- Check persistent paths+ electionExists <- zooExists zk lockPath+ case electionExists of+ Just _ -> return ()+ Nothing -> void (try $ zooCreate zk lockPath Nothing zooOpenAclUnsafe ZooPersistent :: IO (Either ZooException StringCompletion))++ -- Create Ephemeral and Sequece znode, and get the seq number i+ (StringCompletion this) <- createSeqEphemeralZNode zk lockPath guid+ let thisSeqNumWithGUID = mkSequenceNumWithGUID this++ callback zk thisSeqNumWithGUID+ where+ callback zk_ self = do+ (StringsCompletion (StringVector children)) <- zooGetChildren zk_ lockPath+ let childrenSeqNumWithGUID = mkSequenceNumWithGUID <$> children+ case filter (< self) childrenSeqNumWithGUID of+ [] -> do+ return $ unSequenceNumWithGUID self+ xs -> do+ let toWatch = lockPath <> "/" <> unSequenceNumWithGUID (maximum xs)+ zooWatchExists zk_ toWatch (\HsWatcherCtx{..} -> void $ callback watcherCtxZHandle self)+ (\_ -> return ())+ return $ unSequenceNumWithGUID self++-- | To release a distributed lock. Note that the real lock path+-- should be the one acquired by 'lock', otherwise, a 'ZooException'+-- will be thrown.+unlock :: ZHandle+ -- ^ The zookeeper handle obtained by a call to 'zookeeperResInit'+ -> CBytes+ -- ^ The real lock path acquired by 'lock'. An exception will be+ -- thrown if it is bad (for example, does not exist)+ -> IO ()+unlock zk thisLock = zooDelete zk thisLock Nothing
test/Spec.hs view
@@ -3,17 +3,18 @@ module Main where import Control.Concurrent-import Control.Monad (replicateM, void)-import qualified Data.UUID as UUID+import Control.Concurrent.Async (concurrently_)+import Control.Monad (replicateM, void)+import qualified Data.UUID as UUID import Data.UUID.V4-import Data.Version (makeVersion)+import Data.Version (makeVersion) import Foreign.C import Test.Hspec-import qualified Z.Data.CBytes as CB+import qualified Z.Data.CBytes as CB import ZooKeeper import ZooKeeper.Exception-import ZooKeeper.Recipe.Election (election)+import ZooKeeper.Recipe (election, lock, unlock) import ZooKeeper.Types recvTimeout :: CInt@@ -29,6 +30,8 @@ hspec $ propSpec zh hspec $ electionSpec1 zh hspec $ electionSpec2 client+ hspec $ lockSpec1 zh+ hspec $ lockSpec2 client opSpec :: ZHandle -> Spec opSpec zh = do@@ -174,6 +177,43 @@ killThread t1 takeMVar finished4 readMVar leader `shouldReturn` (2 :: Int)++lockSpec1 :: ZHandle -> Spec+lockSpec1 zh = describe "ZooKeeper.zooLock (single session)" $ do+ it "Test single client situation" $ do+ result <- newEmptyMVar+ uuid <- nextRandom+ thisLock <- lock zh "/lock1" (CB.pack . UUID.toString $ uuid)+ putMVar result 1+ unlock zh thisLock+ readMVar result `shouldReturn` (1 :: Int)++lockSpec2 :: Resource ZHandle -> Spec+lockSpec2 client_ = describe "ZooKeeper.zooLock (multi sessions)" $ do+ it "Test multi clients situation" $ do+ result1 <- newMVar 0+ result2 <- newMVar 0+ [uuid1, uuid2] <- replicateM 2 nextRandom++ concurrently_ (action1 result1) (action1 result1)+ readMVar result1 `shouldReturn` (1 :: Int)++ withResource client_ $ \zh1 -> do+ withResource client_ $ \zh2 -> do+ concurrently_ (action2 zh1 (CB.pack . UUID.toString $ uuid1) result2)+ (action2 zh2 (CB.pack . UUID.toString $ uuid2) result2)+ readMVar result2 `shouldReturn` (2 :: Int)+ where+ action1 target = do+ x <- readMVar target+ threadDelay 100000+ swapMVar target (x + 1)+ action2 zh guid target = do+ thisLock <- lock zh "/lock2" guid+ x <- readMVar target+ threadDelay 100000+ _ <- swapMVar target (x + 1)+ unlock zh thisLock -------------------------------------------------------------------------------
zoovisitor.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: zoovisitor-version: 0.1.4.0+version: 0.1.5.0 synopsis: A haskell binding to Apache Zookeeper C library(mt) using Haskell Z project. @@ -32,12 +32,14 @@ exposed-modules: ZooKeeper ZooKeeper.Exception- ZooKeeper.Recipe.Election+ ZooKeeper.Recipe ZooKeeper.Types other-modules: ZooKeeper.Internal.FFI ZooKeeper.Internal.Types+ ZooKeeper.Recipe.Election+ ZooKeeper.Recipe.Lock ZooKeeper.Recipe.Utils build-depends:@@ -80,6 +82,7 @@ main-is: Spec.hs hs-source-dirs: test build-depends:+ , async , base >=4.12 && <5 , hspec , uuid