packages feed

MultiChor-1.0.0.0: examples/KVS6SizePoly.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}

{-
-}

module KVS6SizePoly where

import CLI
import Choreography
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Foldable (toList)
import Data.IORef (IORef)
import Data.IORef qualified as IORef
import Data.List (nub)
import Data.Map (Map)
import Data.Map qualified as Map
import GHC.TypeLits (KnownSymbol)
import Test.QuickCheck (Arbitrary, arbitrary, frequency, listOf)
import Text.Read (readMaybe)

readIORef :: (MonadIO m) => IORef a -> m a
readIORef = liftIO <$> IORef.readIORef

modifyIORef :: (MonadIO m) => IORef a -> (a -> a) -> m a
modifyIORef ref f = do
  a <- readIORef ref
  liftIO $ IORef.modifyIORef ref f
  return a

newIORef :: (MonadIO m) => a -> m (IORef a)
newIORef = liftIO <$> IORef.newIORef

-- $(mkLoc "client")

-- $(mkLoc "primary")

-- $(mkLoc "backup1")

-- $(mkLoc "backup2")
-- type Participants = ["client", "primary", "backup1", "backup2"]

kvs :: (KnownSymbol client) => ReplicationStrategy ps (CLI m) -> Member client ps -> Choreo ps (CLI m) ()
kvs ReplicationStrategy {setup, primary, handle} client = do
  rigging <- setup
  let go = do
        request <- (client, readRequest) -~> primary @@ nobody
        response <- handle rigging singleton request
        case response of
          Stopped -> return ()
          _ -> do
            client `_locally_` putOutput "Recieved:" response
            go
  go

naryReplicationStrategy ::
  (KnownSymbol primary, KnownSymbols backups, KnownSymbols ps, MonadIO m) =>
  Member primary ps ->
  Subset backups ps ->
  ReplicationStrategy ps m
naryReplicationStrategy primary backups =
  ReplicationStrategy
    { primary,
      setup = servers `_parallel` newIORef (Map.empty :: State),
      handle = \stateRef pHas request -> do
        request' <- (primary, (pHas, request)) ~> servers
        localResponse <-
          servers `parallel` \server un ->
            handleRequest (viewFacet un server stateRef) (un server request')
        responses <- gather servers (primary @@ nobody) localResponse
        response <-
          (primary @@ nobody) `congruently` \un ->
            case nub (toList $ un refl responses) of
              [r] -> r
              rs -> Desynchronization rs
        broadcast (primary, response)
    }
  where
    servers = primary @@ backups

data ReplicationStrategy ps m
  = forall primary rigging.
  (KnownSymbol primary) =>
  ReplicationStrategy
  { primary :: Member primary ps,
    setup :: Choreo ps m rigging,
    handle ::
      forall starts.
      rigging ->
      Member primary starts ->
      Located starts Request ->
      Choreo ps m Response
  }

data Request = Put String String | Get String | Stop deriving (Eq, Ord, Read, Show)

data Response = Found String | NotFound | Stopped | Desynchronization [Response]
  deriving (Eq, Ord, Read, Show)

-- | PUT returns the old stored value; GET returns whatever was stored.
handleRequest :: (MonadIO m) => IORef State -> Request -> m Response
handleRequest stateRef (Put key value) = mlookup key <$> modifyIORef stateRef (Map.insert key value)
handleRequest stateRef (Get key) = mlookup key <$> readIORef stateRef
handleRequest _ Stop = return Stopped

mlookup :: String -> State -> Response
mlookup key = maybe NotFound Found . Map.lookup key

type State = Map String String

newtype Args = Args [Request] deriving (Eq, Ord, Read, Show)

instance Arbitrary Args where
  arbitrary = do
    reqs <- pgs
    return . Args $ reqs ++ [Stop]
    where
      pgs =
        listOf $
          frequency
            [ (1, Put <$> arbitrary <*> arbitrary),
              (1, Get <$> arbitrary)
            ]

readRequest :: CLI m Request
readRequest = do
  line <- getstr "Command?"
  case line of
    [] -> return Stop
    _ -> case readMaybe line of
      Just t -> return t
      Nothing -> putNote "Invalid command" >> readRequest

-- | `nullReplicationStrategy` is a replication strategy that does not replicate the state.
nullReplicationStrategy ::
  (KnownSymbol primary, KnownSymbols ps, MonadIO m) =>
  Member primary ps ->
  ReplicationStrategy ps m
nullReplicationStrategy primary =
  ReplicationStrategy
    { primary,
      setup = primary `_locally` newIORef (Map.empty :: State),
      handle = \stateRef pHas request ->
        ( (primary, \un -> handleRequest (un singleton stateRef) (un pHas request)) ~~> refl
        )
          >>= naked refl
    }

naryHumans ::
  (KnownSymbol primary, KnownSymbols backups, KnownSymbols ps, MonadIO m) =>
  Member primary ps ->
  Subset backups ps ->
  ReplicationStrategy ps (CLI m)
naryHumans primary backups =
  ReplicationStrategy
    { primary,
      setup = primary `_locally` newIORef (Map.empty :: State),
      handle = \stateRef pHas request -> do
        request' <- (primary, (pHas, request)) ~> backups
        backupResponse <- backups `parallel` \server un -> readResponse (un server request')
        localResponse <- primary `locally` \un -> handleRequest (un singleton stateRef) (un pHas request)
        responses <- gather backups (primary @@ nobody) backupResponse
        response <-
          (primary @@ nobody) `congruently` \un ->
            case nub $ un refl localResponse : toList (un refl responses) of
              [r] -> r
              rs -> Desynchronization rs
        ((primary, response) ~> refl) >>= naked refl
    }
  where
    readResponse :: Request -> CLI m Response
    readResponse r = do
      line <- getstr $ show r ++ ": "
      case line of
        [] -> return NotFound
        _ -> return $ Found line

{-main :: IO ()
main = do
  [loc] <- getArgs
  case loc of
    "client" -> runChoreography config primaryBackupChoreo "client"
    "primary" -> runChoreography config primaryBackupChoreo "primary"
    "backup1" -> runChoreography config primaryBackupChoreo "backup1"
    "backup2" -> runChoreography config primaryBackupChoreo "backup2"
    _ -> error "unknown party"
  return ()
  where
    config =
      mkHttpConfig
        [ ("client", ("localhost", 3000)),
          ("primary", ("localhost", 4000)),
          ("backup1", ("localhost", 5000)),
          ("backup2", ("localhost", 6000))
        ]

-}