module Linearisability
( History
, linearisable
, trace
, wellformed
)
where
import Control.Distributed.Process
(ProcessId)
import Data.Tree
(Forest, Tree(Node))
import Text.Printf
(printf)
type History pid inv resp = [(pid, Either inv resp)]
data Operation pid inv resp = Operation pid inv resp
deriving Show
takeInvocations :: History pid inv resp -> [(pid, inv)]
takeInvocations [] = []
takeInvocations ((pid, Left inv) : hist) = (pid, inv) : takeInvocations hist
takeInvocations ((_, Right _) : _) = []
findResponse :: Eq pid => pid -> History pid inv resp -> [(resp, History pid inv resp)]
findResponse _ [] = []
findResponse pid ((pid', Right resp) : es) | pid == pid' = [(resp, es)]
findResponse pid (e : es) =
[ (resp, e : es') | (resp, es') <- findResponse pid es ]
interleavings :: Eq pid => History pid inv resp -> Forest (Operation pid inv resp)
interleavings [] = []
interleavings es =
[ Node (Operation pid inv resp) (interleavings es')
| (pid, inv) <- takeInvocations es
, (resp, es') <- findResponse pid (filter1 (not . matchInvocation pid) es)
]
where
matchInvocation pid (pid', Left _) = pid == pid'
matchInvocation _ _ = False
filter1 :: (a -> Bool) -> [a] -> [a]
filter1 _ [] = []
filter1 p (x : xs) | p x = x : filter1 p xs
| otherwise = xs
linearisable
:: Eq pid
=> (model -> Either inv resp -> model)
-> (model -> inv -> resp -> Bool)
-> model
-> History pid inv resp
-> Bool
linearisable _ _ _ [] = True
linearisable transition postcondition model0 es =
any (step model0) (interleavings es)
where
step model (Node (Operation _ inv resp) roses) =
postcondition model inv resp &&
any' (step (transition (transition model (Left inv)) (Right resp))) roses
where
any' :: (a -> Bool) -> [a] -> Bool
any' _ [] = True
any' p xs = any p xs
prettyPrintProcessId :: ProcessId -> String
prettyPrintProcessId = reverse . takeWhile (/= ':') . reverse . show
trace
:: (Show model, Show inv, Show resp)
=> (model -> Either inv resp -> model) -> model -> History ProcessId inv resp -> String
trace transitions = go
where
go _ [] = ""
go model ((pid, Left inv) : hist) =
printf "%s\n ==> %s [%s]\n%s"
(show model)
(show inv)
(prettyPrintProcessId pid)
(go (transitions model (Left inv)) hist)
go model ((pid, Right resp) : hist) =
printf "%s\n <== %s [%s]\n%s"
(show model)
(show resp)
(prettyPrintProcessId pid)
(go (transitions model (Right resp)) hist)
data NotSequential pid inv resp
= FirstEventIsntInvocation pid resp
| InvocationFollowedByInvocation pid inv pid inv
| InvocationFollowedByNonMatchingResponse pid inv pid resp
| ResponseFollowedByResponse pid resp pid resp
| ResponseFollowedByInvocation pid resp pid inv
| LoneResponse pid resp
deriving Show
processSubhistory :: Eq pid => pid -> History pid inv resp -> History pid inv resp
processSubhistory pid = filter ((== pid) . fst)
isSequential :: Eq pid => History pid inv resp -> Either (NotSequential pid inv resp) ()
isSequential history = case history of
(pid, Right resp) : _ -> Left (FirstEventIsntInvocation pid resp)
_ -> go history
where
go []
= Right ()
go [(pid, Right resp)]
= Left (LoneResponse pid resp)
go [(_, Left _)]
= Right ()
go ((pid, Left inv) : (pid', Right resp) : hist)
| pid == pid' = go hist
| otherwise = Left (InvocationFollowedByNonMatchingResponse pid inv pid' resp)
go ((pid, Left inv) : (pid', Left inv') : _)
= Left (InvocationFollowedByInvocation pid inv pid' inv')
go ((pid, Right resp) : (pid', Right resp') : _)
= Left (ResponseFollowedByResponse pid resp pid' resp')
go ((pid, Right resp) : (pid', Left inv) : _)
= Left (ResponseFollowedByInvocation pid resp pid' inv)
wellformed :: Eq pid => [pid] -> History pid inv resp -> Either (NotSequential pid inv resp) ()
wellformed pids history = allRight
[ isSequential (processSubhistory pid history) | pid <- pids ]
where
allRight :: [Either a b] -> Either a ()
allRight = foldr (>>) (Right ())