packages feed

crdt 4.2 → 5.0

raw patch · 9 files changed

+108/−73 lines, 9 filesdep +stmPVP ok

version bump matches the API change (PVP)

Dependencies added: stm

API changes (from Hackage documentation)

- CRDT.LamportClock: LamportClock :: (State (Map Pid LocalTime) a) -> LamportClock a
- CRDT.LamportClock: Process :: (ReaderT Pid LamportClock a) -> Process a
- CRDT.LamportClock: getRealLamportTime :: IO LamportTime
- CRDT.LamportClock: instance CRDT.LamportClock.Clock CRDT.LamportClock.Process
- CRDT.LamportClock: instance GHC.Base.Applicative CRDT.LamportClock.Process
- CRDT.LamportClock: instance GHC.Base.Functor CRDT.LamportClock.Process
- CRDT.LamportClock: instance GHC.Base.Monad CRDT.LamportClock.Process
- CRDT.LamportClock: newtype LamportClock a
- CRDT.LamportClock: newtype Process a
- CRDT.LamportClock: runLamportClock :: LamportClock a -> a
- CRDT.LamportClock: runProcess :: Pid -> Process a -> LamportClock a
+ CRDT.LamportClock: LamportClockSim :: (State (Map Pid LocalTime) a) -> LamportClockSim a
+ CRDT.LamportClock: ProcessSim :: (ReaderT Pid LamportClockSim a) -> ProcessSim a
+ CRDT.LamportClock: class Monad m => Process m
+ CRDT.LamportClock: instance CRDT.LamportClock.Clock CRDT.LamportClock.LamportClock
+ CRDT.LamportClock: instance CRDT.LamportClock.Clock CRDT.LamportClock.ProcessSim
+ CRDT.LamportClock: instance CRDT.LamportClock.Process CRDT.LamportClock.LamportClock
+ CRDT.LamportClock: instance CRDT.LamportClock.Process CRDT.LamportClock.ProcessSim
+ CRDT.LamportClock: instance Control.Monad.IO.Class.MonadIO CRDT.LamportClock.LamportClock
+ CRDT.LamportClock: instance GHC.Base.Applicative CRDT.LamportClock.LamportClockSim
+ CRDT.LamportClock: instance GHC.Base.Applicative CRDT.LamportClock.ProcessSim
+ CRDT.LamportClock: instance GHC.Base.Functor CRDT.LamportClock.LamportClockSim
+ CRDT.LamportClock: instance GHC.Base.Functor CRDT.LamportClock.ProcessSim
+ CRDT.LamportClock: instance GHC.Base.Monad CRDT.LamportClock.LamportClockSim
+ CRDT.LamportClock: instance GHC.Base.Monad CRDT.LamportClock.ProcessSim
+ CRDT.LamportClock: newtype LamportClockSim a
+ CRDT.LamportClock: newtype ProcessSim a
+ CRDT.LamportClock: runLamportClockSim :: LamportClockSim a -> a
+ CRDT.LamportClock: runProcessSim :: Pid -> ProcessSim a -> LamportClockSim a
- CRDT.Cm: makeOp :: (CmRDT op, Intent op ~ op) => Intent op -> Payload op -> Maybe (Process op)
+ CRDT.Cm: makeOp :: (CmRDT op, Intent op ~ op, Applicative m) => Intent op -> Payload op -> Maybe (m op)
- CRDT.Cv.LwwElementSet: add :: Ord a => a -> LwwElementSet a -> Process (LwwElementSet a)
+ CRDT.Cv.LwwElementSet: add :: (Ord a, Clock m) => a -> LwwElementSet a -> m (LwwElementSet a)
- CRDT.Cv.LwwElementSet: remove :: Ord a => a -> LwwElementSet a -> Process (LwwElementSet a)
+ CRDT.Cv.LwwElementSet: remove :: (Ord a, Clock m) => a -> LwwElementSet a -> m (LwwElementSet a)
- CRDT.Cv.ORSet: add :: Ord a => a -> ORSet a -> Process (ORSet a)
+ CRDT.Cv.ORSet: add :: (Ord a, Process m) => a -> ORSet a -> m (ORSet a)
- CRDT.LamportClock: class Monad m => Clock m
+ CRDT.LamportClock: class Process m => Clock m
- CRDT.LamportClock: getPid :: Process Pid
+ CRDT.LamportClock: getPid :: Process m => m Pid

Files

crdt.cabal view
@@ -2,17 +2,17 @@ -- -- see: https://github.com/sol/hpack ----- hash: c7858c1e62913fddff18ba02c93949c604e502f20eced23a4b1d57b0c89d199b+-- hash: 35a8e5d951dbc5cec14577596c73f14d73ebf453af76b242e01beea8cf404d5c  name:           crdt-version:        4.2+version:        5.0 synopsis:       Conflict-free replicated data types description:    Definitions of CmRDT and CvRDT. Implementations for some classic CRDTs. category:       Distributed Systems homepage:       https://github.com/cblp/crdt#readme bug-reports:    https://github.com/cblp/crdt/issues maintainer:     Yuriy Syrovetskiy <cblp@cblp.su>-copyright:      2017 Yuriy Syrovetskiy, Nikolay Loginov+copyright:      2017 Yuriy Syrovetskiy, Nikolay Loginov; 2018 Yuriy Syrovetskiy license:        BSD3 license-file:   LICENSE build-type:     Simple@@ -33,6 +33,7 @@     , mtl     , network-info     , safe+    , stm     , time   exposed-modules:       CRDT.Cm
lib/CRDT/Cm.hs view
@@ -8,7 +8,7 @@     , concurrent     ) where -import           CRDT.LamportClock (Process)+import           CRDT.LamportClock (Clock)  -- | Partial order for causal semantics. -- Values of some type may be ordered and causally-ordered different ways.@@ -64,10 +64,11 @@     -- | Generate an update to the local and remote replicas.     --     -- Returns 'Nothing' if the intended operation is not applicable.-    makeOp :: Intent op -> Payload op -> Maybe (Process op)+    makeOp :: Clock m => Intent op -> Payload op -> Maybe (m op)      default makeOp-        :: Intent op ~ op => Intent op -> Payload op -> Maybe (Process op)+        :: (Intent op ~ op, Applicative m)+        => Intent op -> Payload op -> Maybe (m op)     makeOp i _ = Just $ pure i      -- | Apply an update to the payload (downstream).
lib/CRDT/Cv/LwwElementSet.hs view
@@ -16,7 +16,7 @@ import qualified Data.Map.Strict as Map import           Data.Semigroup (Semigroup (..)) -import           CRDT.LamportClock (Process)+import           CRDT.LamportClock (Clock) import           CRDT.LWW (LWW, advanceFromLWW) import qualified CRDT.LWW as LWW import           Data.Semilattice (Semilattice)@@ -32,13 +32,13 @@ initial :: LwwElementSet a initial = LES Map.empty -add :: Ord a => a -> LwwElementSet a -> Process (LwwElementSet a)+add :: (Ord a, Clock m) => a -> LwwElementSet a -> m (LwwElementSet a) add value old@(LES m) = do     advanceFromLES old     tag <- LWW.initial True     pure . LES $ Map.insertWith (<>) value tag m -remove :: Ord a => a -> LwwElementSet a -> Process (LwwElementSet a)+remove :: (Ord a, Clock m) => a -> LwwElementSet a -> m (LwwElementSet a) remove value old@(LES m) = do     advanceFromLES old     tag <- LWW.initial False@@ -47,5 +47,5 @@ lookup :: Ord a => a -> LwwElementSet a -> Bool lookup value (LES m) = maybe False LWW.query $ Map.lookup value m -advanceFromLES :: LwwElementSet a -> Process ()+advanceFromLES :: Clock m => LwwElementSet a -> m () advanceFromLES (LES m) = for_ m advanceFromLWW
lib/CRDT/Cv/ORSet.hs view
@@ -33,7 +33,7 @@ initial :: ORSet a initial = ORSet Map.empty -add :: Ord a => a -> ORSet a -> Process (ORSet a)+add :: (Ord a, Process m) => a -> ORSet a -> m (ORSet a) add a (ORSet s) = do     pid <- getPid     pure $ ORSet $ Map.alter (add1 pid) a s
lib/CRDT/LamportClock.hs view
@@ -5,16 +5,19 @@     -- * Lamport timestamp (for a single process)     , Clock (..)     , LamportTime (..)-    , getRealLamportTime     -- * Lamport clock (for a whole multi-process system)-    , LamportClock (..)-    , runLamportClock+    , LamportClockSim (..)+    , runLamportClockSim     -- * Process     , Process (..)-    , getPid-    , runProcess+    -- * ProcessSim+    , ProcessSim (..)+    , runProcessSim     ) where +import           Control.Concurrent.STM (TVar, atomically, modifyTVar',+                                         readTVar, writeTVar)+import           Control.Monad.IO.Class (MonadIO, liftIO) import           Control.Monad.Reader (ReaderT, ask, runReaderT) import           Control.Monad.State.Strict (State, evalState, modify, state) import           Control.Monad.Trans (lift)@@ -43,37 +46,36 @@ newtype Pid = Pid Word64     deriving (Eq, Ord, Show) --- | Key is 'Pid'. Non-present value is equivalent to 0.-newtype LamportClock a = LamportClock (State (Map Pid LocalTime) a)+-- | Lamport clock simpulation. Key is 'Pid'.+-- Non-present value is equivalent to 0.+newtype LamportClockSim a = LamportClockSim (State (Map Pid LocalTime) a)     deriving (Applicative, Functor, Monad) -newtype Process a = Process (ReaderT Pid LamportClock a)+-- | ProcessSim inside Lamport clock simpulation.+newtype ProcessSim a = ProcessSim (ReaderT Pid LamportClockSim a)     deriving (Applicative, Functor, Monad) -getPid :: Process Pid-getPid = Process ask+class Monad m => Process m where+    getPid :: m Pid -runLamportClock :: LamportClock a -> a-runLamportClock (LamportClock action) = evalState action mempty+runLamportClockSim :: LamportClockSim a -> a+runLamportClockSim (LamportClockSim action) = evalState action mempty -runProcess :: Pid -> Process a -> LamportClock a-runProcess pid (Process action) = runReaderT action pid+runProcessSim :: Pid -> ProcessSim a -> LamportClockSim a+runProcessSim pid (ProcessSim action) = runReaderT action pid -preIncrementAt :: Pid -> LamportClock LocalTime+preIncrementAt :: Pid -> LamportClockSim LocalTime preIncrementAt pid =-    LamportClock . state $ \m -> let+    LamportClockSim . state $ \m -> let         lt' = succ . fromMaybe 0 $ Map.lookup pid m         in (lt', Map.insert pid lt' m)  getRealLocalTime :: IO LocalTime getRealLocalTime = round . utcTimeToPOSIXSeconds <$> getCurrentTime --- TODO(cblp, 2018-01-05) monotonic-getRealLamportTime :: IO LamportTime-getRealLamportTime =-    LamportTime <$> getRealLocalTime <*> (Pid . decodeMac <$> getMac)+getPidByMac :: IO Pid+getPidByMac = Pid . decodeMac <$> getMac   where-     getMac :: IO MAC     getMac =         headDef (error "Can't get any non-zero MAC address of this machine")@@ -85,15 +87,43 @@     decodeMac (MAC b5 b4 b3 b2 b1 b0) =         decode $ BSL.pack [0, 0, b5, b4, b3, b2, b1, b0] -class Monad m => Clock m where+class Process m => Clock m where     getTime :: m LamportTime     advance :: LocalTime -> m () -instance Clock Process where-    getTime = Process $ do+instance Process ProcessSim where+    getPid = ProcessSim ask++instance Clock ProcessSim where+    getTime = ProcessSim $ do         pid <- ask         time <- lift $ preIncrementAt pid         pure $ LamportTime time pid-    advance time = Process $ do++    advance time = ProcessSim $ do         pid <- ask-        lift . LamportClock . modify $ Map.insertWith max pid time+        lift . LamportClockSim . modify $ Map.insertWith max pid time++-- TODO(cblp, 2018-01-06) benchmark and compare with 'atomicModifyIORef'+newtype LamportClock a = LamportClock (ReaderT (TVar LocalTime) IO a)+    deriving (Applicative, Functor, Monad, MonadIO)++instance Process LamportClock where+    getPid = liftIO getPidByMac++instance Clock LamportClock where+    advance time = LamportClock $ do+        timeVar <- ask+        lift $ atomically $ modifyTVar' timeVar $ max time++    getTime = LamportClock $ do+        timeVar <- ask+        lift $ do+            realTime <- getRealLocalTime+            time1 <- atomically $ do+                time0 <- readTVar timeVar+                let time1 = max realTime (time0 + 1)+                writeTVar timeVar time1+                pure time1+            pid <- getPidByMac+            pure $ LamportTime time1 pid
test/LWW.hs view
@@ -14,7 +14,7 @@ import           Data.Semigroup ((<>)) import           Test.QuickCheck ((===)) -import           CRDT.LamportClock (runLamportClock, runProcess)+import           CRDT.LamportClock (runLamportClockSim, runProcessSim) import           CRDT.LWW (LWW, assign, initial, query)  import           Laws (cmrdtLaw, cvrdtLaws)@@ -23,13 +23,14 @@  test_Cv = cvrdtLaws @(LWW Char) -prop_assign pid1 pid2 (formerValue :: Char) latterValue = runLamportClock $ do-    state1 <- runProcess pid1 $ initial formerValue-    state2 <- runProcess pid2 $ assign latterValue state1-    pure $ query state2 === latterValue+prop_assign pid1 pid2 (formerValue :: Char) latterValue =+    runLamportClockSim $ do+        state1 <- runProcessSim pid1 $ initial formerValue+        state2 <- runProcessSim pid2 $ assign latterValue state1+        pure $ query state2 === latterValue  prop_merge_with_former pid1 pid2 (formerValue :: Char) latterValue =-    runLamportClock $ do-        state1 <- runProcess pid1 $ initial formerValue-        state2 <- runProcess pid2 $ assign latterValue state1+    runLamportClockSim $ do+        state1 <- runProcessSim pid1 $ initial formerValue+        state2 <- runProcessSim pid2 $ assign latterValue state1         pure $ query (state1 <> state2) === latterValue
test/Laws.hs view
@@ -23,7 +23,8 @@ import           CRDT.Cm.GSet (GSet) import           CRDT.Cm.TwoPSet (TwoPSet) import           CRDT.Cv (CvRDT)-import           CRDT.LamportClock (Process, runLamportClock, runProcess)+import           CRDT.LamportClock (Clock, ProcessSim, runLamportClockSim,+                                    runProcessSim) import           CRDT.LWW (LWW) import qualified CRDT.LWW as LWW import           Data.Semilattice (Semilattice, merge)@@ -53,9 +54,10 @@     type Initial op     type Initial op = Payload op -    initialize :: Initial op -> Process (Payload op)+    initialize :: Clock m => Initial op -> m (Payload op)     default initialize-        :: Initial op ~ Payload op => Initial op -> Process (Payload op)+        :: (Applicative m, Initial op ~ Payload op)+        => Initial op -> m (Payload op)     initialize = pure  instance Initialize (Counter a)@@ -82,18 +84,18 @@ cmrdtLaw = property concurrentOpsCommute   where     concurrentOpsCommute st1 st2 seed3 in1 in2 pid1 pid2 pid3 =-        let (op1, op2, state) = runLamportClock $ (,,)-                <$> runProcess pid1 (makeOp @op in1 st1 `orElse` discard)-                <*> runProcess pid2 (makeOp @op in2 st2 `orElse` discard)-                <*> runProcess pid3 (initialize @op seed3)+        let (op1, op2, state) = runLamportClockSim $ (,,)+                <$> runProcessSim pid1 (makeOp @op in1 st1 `orElse` discard)+                <*> runProcessSim pid2 (makeOp @op in2 st2 `orElse` discard)+                <*> runProcessSim pid3 (initialize @op seed3)         in  concurrent op1 op2 ==> opCommutativity (in1, op1) (in2, op2) state     opCommutativity (in1, op1) (in2, op2) state =-        isJust (makeOp @op in1 state) ==>-        isJust (makeOp @op in2 state) ==>+        isJust (makeOp @op @ProcessSim in1 state) ==>+        isJust (makeOp @op @ProcessSim in2 state) ==>             counterexample                 ( show in1 ++ " must be valid after " ++ show op2 ++                   " applied to " ++ show state )-                (isJust $ makeOp @op in1 $ apply op2 state)+                (isJust $ makeOp @op @ProcessSim in1 $ apply op2 state)             .&&.             (apply op1 . apply op2) state === (apply op2 . apply op1) state 
test/LwwElementSet.hs view
@@ -15,35 +15,35 @@  import           Data.Semigroup ((<>)) -import           CRDT.Cv.LwwElementSet (LwwElementSet (..), add, lookup, remove)-import           CRDT.LamportClock (runLamportClock, runProcess)+import           CRDT.Cv.LwwElementSet (LwwElementSet, add, lookup, remove)+import           CRDT.LamportClock (runLamportClockSim, runProcessSim)  import           Laws (cvrdtLaws)  test_Cv = cvrdtLaws @(LwwElementSet Char)  prop_add (s :: LwwElementSet Char) x pid1 =-    runLamportClock $ do-        s1 <- runProcess pid1 $ add x s+    runLamportClockSim $ do+        s1 <- runProcessSim pid1 $ add x s         pure $ lookup x s1  prop_remove (s :: LwwElementSet Char) x pid1 pid2 =-    runLamportClock $ do-        s1 <- runProcess pid1 $ add x s-        s2 <- runProcess pid2 $ remove x s1+    runLamportClockSim $ do+        s1 <- runProcessSim pid1 $ add x s+        s2 <- runProcessSim pid2 $ remove x s1         pure . not $ lookup x s2  -- | Difference from 'TwoPSet' -- no removal bias prop_no_removal_bias (s :: LwwElementSet Char) x pid1 pid2 pid3 =-    runLamportClock $ do-        s1 <- runProcess pid1 $ add x s-        s2 <- runProcess pid2 $ remove x s1-        s3 <- runProcess pid3 $ add x s2+    runLamportClockSim $ do+        s1 <- runProcessSim pid1 $ add x s+        s2 <- runProcessSim pid2 $ remove x s1+        s3 <- runProcessSim pid3 $ add x s2         pure $ lookup x s3  -- | Difference from 'ORSet' -- other replica can accidentally delete x prop_they_accidentally_delete_our_value (s :: LwwElementSet Char) x pid1 pid2 =-    runLamportClock $ do-        s1 <- runProcess pid1 $ add x s-        s2 <- runProcess pid2 $ remove x =<< add x s+    runLamportClockSim $ do+        s1 <- runProcessSim pid1 $ add x s+        s2 <- runProcessSim pid2 $ remove x =<< add x s         pure . not . lookup x $ s1 <> s2
test/ORSet.hs view
@@ -9,17 +9,17 @@  import           Data.Semigroup (Semigroup ((<>))) -import           CRDT.Cv.ORSet (ORSet (..), add, lookup, remove)-import           CRDT.LamportClock (runLamportClock, runProcess)+import           CRDT.Cv.ORSet (ORSet, add, lookup, remove)+import           CRDT.LamportClock (runLamportClockSim, runProcessSim)  import           Laws (cvrdtLaws)  test_Cv = cvrdtLaws @(ORSet Int)  prop_add pid (x :: Char) s =-    runLamportClock $ runProcess pid $ not . lookup x . remove x <$> add x s+    runLamportClockSim $ runProcessSim pid $ not . lookup x . remove x <$> add x s  -- | Difference from 'LwwElementSet' -- -- other replica can not accidentally delete x prop_add_merge (x :: Char) pid s1 s0 =-    runLamportClock $ runProcess pid $ lookup x . (<> s1) <$> add x s0+    runLamportClockSim $ runProcessSim pid $ lookup x . (<> s1) <$> add x s0