packages feed

cloudchor-0.1.0.0: bench/Choreos/Kvs.hs

{-# LANGUAGE BlockArguments      #-}
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE DerivingStrategies  #-}
{-# LANGUAGE ImpredicativeTypes  #-}
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- This is a version of the `kvs-2-primary-backup` choreography, adapted for the benchmarking setup.
module Choreos.Kvs where

import Data.Foldable(for_)
import Data.IORef
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Proxy

import Benchmark
import Choreography hiding (cond, cond_, cond', cond_')
import Choreography.Location(wrap)

client :: Proxy "client"
client = Proxy

primary :: Proxy "primary"
primary = Proxy

backup :: Proxy "backup"
backup = Proxy

type State = Map String String

data Request = Put String String | Get String deriving stock (Show, Read)

isPutRequest :: Request -> Bool
isPutRequest (Put _ _) = True
isPutRequest _ = False

requests :: [Request]
requests =
  [ Put "x" "a"
  , Put "y" "b"
  , Get "x"
  , Put "y" "c"
  , Get "y"
  ]

type Response = Maybe String

-- | `handleRequest` handle a request and returns the new the state.
handleRequest :: Request -> IORef State -> IO Response
handleRequest request stateRef = case request of
  Put key value -> do
    modifyIORef stateRef (Map.insert key value)
    return (Just value)
  Get key -> do
    state <- readIORef stateRef
    return (Map.lookup key state)

-- | `kvs` is a choreography that processes a single request located at the client and returns the response.
-- If the request is a `PUT`, it will forward the request to the backup node.
kvs ::
  Cond_' ->
  Request @ "client" ->
  IORef State @ "primary" ->
  IORef State @ "backup" ->
  Choreo IO (Response @ "client")
kvs cond_' request primaryStateRef backupStateRef = do
  -- send request to the primary node
  request' <- (client, request) ~> primary

  -- branch on the request
  cond_' (primary, \unwrap -> pure $ isPutRequest $ unwrap request') \case
    -- if the request is a `PUT`, forward the request to the backup node
    True -> do
      request'' <- (primary, request') ~> backup
      ack <-
        backup `locally` \unwrap -> do
          handleRequest (unwrap request'') (unwrap backupStateRef)
      (backup, ack) ~> primary
      return $ wrap ()
    _ -> do
      return $ wrap ()

  -- process request on the primary node
  response <-
    primary `locally` \unwrap ->
      handleRequest (unwrap request') (unwrap primaryStateRef)

  -- send response to client
  (primary, response) ~> client

kvs' :: Cond_' -> Choreo IO ()
kvs' cond_' = do
  primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)
  backupStateRef <- backup `locally` \_ -> newIORef (Map.empty :: State)
  for_ requests \request -> do
    -- Replaced the interactive prompt with a dummy `locally` to keep the choreography's structure,
    -- for testing the overhead of `cond_`'s static analysis.
    clientRequest <- client `locally` \_ -> pure request
    kvs cond_' clientRequest primaryStateRef backupStateRef
    client `locally` \_ -> pure () -- putStrLn ("> " ++ show (unwrap response)) -- Don't print during benchmarking
    pure ()