packages feed

tpar-0.1.0.0: TPar/Rpc.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module TPar.Rpc where

import Control.Distributed.Process
import Control.Distributed.Process.Serializable
import Data.Binary

newtype RpcSendPort a b = RpcSendPort (SendPort (a, SendPort b))
                        deriving (Show, Binary, Serializable)
newtype RpcRecvPort a b = RpcRecvPort (ReceivePort (a, SendPort b) )

newRpc :: (Serializable a, Serializable b)
       => Process (RpcSendPort a b, RpcRecvPort a b)
newRpc = do
    (sp, rp) <- newChan
    return (RpcSendPort sp, RpcRecvPort rp)

-- | Call a remote procedure or return 'Left' if the handling process is no
-- longer alive.
callRpc :: (Serializable a, Serializable b)
        => RpcSendPort a b -> a -> Process (Either DiedReason b)
callRpc (RpcSendPort sp) x = do
    (reply_sp, reply_rp) <- newChan
    mref <- monitorPort sp
    sendChan sp (x, reply_sp)
    receiveWait [ matchIf (\(PortMonitorNotification mref' _ _) -> mref == mref')
                          (\(PortMonitorNotification _ _ reason) -> pure $ Left reason)
                , matchChan reply_rp (pure . Right)
                ]

matchRpc :: (Serializable a, Serializable b)
         => RpcRecvPort a b -> (a -> Process (b, c)) -> Match c
matchRpc rp handler = matchRpc' rp $ \_ x reply -> do
    (y, z) <- handler x
    reply y
    return z

type RpcHandler a b c
    = ProcessId          -- ^ 'ProcessId' of the requestor
   -> a                  -- ^ arguments
   -> (b -> Process ())  -- ^ reply action
   -> Process c          -- ^ return

-- | Allow deferred replies
matchRpc' :: (Serializable a, Serializable b)
          => RpcRecvPort a b -> RpcHandler a b c -> Match c
matchRpc' (RpcRecvPort rp) handler = matchChan rp $ \(x, reply_sp) -> do
    let requestor_pid = sendPortProcessId $ sendPortId reply_sp
    handler requestor_pid x (sendChan reply_sp)