packages feed

zoovisitor-0.1.5.0: src/ZooKeeper/Recipe/Lock.hs

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