packages feed

montage-0.1: Network/Riak/Montage/Protocol.hs

module Network.Riak.Montage.Protocol where

import System.ZMQ
import System.UUID.V4 (uuid)
import Control.Monad (forever)
import Control.Concurrent (forkIO)
import qualified Data.ByteString.Lazy.Char8 as B
import qualified Data.ByteString.Lazy as BW
import qualified Data.ByteString.Char8 as S
import Control.Exception (try, SomeException)
import Text.ProtocolBuffers.WireMessage (messageGet, messagePut)
import Text.ProtocolBuffers.Basic (uFromString)

import Network.Riak.Montage.Util

import Network.StatsWeb (Stats)

import Network.Riak.Montage.Proto.Montage.MontageEnvelope as ME
import Network.Riak.Montage.Proto.Montage.MontageWireMessages
import Network.Riak.Montage.Proto.Montage.MontageError
import Network.Riak.Montage.Types
import Network.Riak.Montage.Process (processRequest,
                                    serializeResponse, ConcurrentState(..))


type ZmqHandler = (S.ByteString -> (BW.ByteString -> IO ()) -> IO ())

runZmqRpc :: String
          -> ZmqHandler
          -> IO ()
runZmqRpc bindSpec call = do
    withContext 1 (\c ->
        runZmqRpcWithContext c bindSpec call)

runZmqRpcWithContext :: Context
                     -> String
                     -> ZmqHandler
                     -> IO ()
runZmqRpcWithContext ctx binda call = do
    withSocket ctx Router (\s -> do
        rand <- uuid
        let inproc = "inproc://" ++ (show rand)
        bind s binda
        bind s inproc
        forever $ do
            zid <- receive s []
            _ <- receive s []
            m <- receive s []
            more <- moreToReceive s
            if more
            then do  -- forward
                fwid <- receive s []
                send s fwid [SndMore]
                send s "" [SndMore]
                send s m []
            else do -- call
                _ <- forkIO $ call m (zmqRpcReply ctx inproc zid)
                return ()
        )

zmqRpcReply :: Context
            -> String        -- inproc
            -> S.ByteString  -- sent id
            -> BW.ByteString  -- out message
            -> IO ()
zmqRpcReply c inproc retid out = do
    withSocket c Req (\s -> do
        connect s inproc
        send' s out [SndMore]
        send s retid []
        )

serveMontageZmq :: (MontageRiakValue r) =>
                   (MontageEnvelope -> ChainCommand r) ->
                   String -> ConcurrentState -> LogCallback ->
                   PoolChooser -> Stats -> IO ()
serveMontageZmq generate runOn state logCB chooser' stats = do
    runZmqRpc runOn wrapMontage
  where
    wrapMontage m cb = do
        case messageGet $ sTl m of
            Right (env, x) | B.length x == 0 -> do
                res <- try $ do
                    let !cmd = generate env
                    fmap (serializeResponse env) $ processRequest state logCB chooser' cmd stats
                case res of
                    Left (e :: SomeException) -> returnError (show e) $ msgid env
                    Right outenv -> cb $  messagePut outenv

            _ -> returnError "Failed to decode MontageEnvelope" Nothing
      where
        returnError err msgid' = do
            logError err
            cb $ messagePut $ MontageEnvelope {
                  mtype = MONTAGE_ERROR
                , msg = messagePut $ MontageError (uFromString err)
                , msgid = msgid'
                }