Begin safe low-level Haskell layer (#7)
* grpc_server_request_call
* basic slice functionality
* rename function to emphasize side effects
* add docs
* ByteBuffer function bindings
* replace unsafeCoerce with more specific function, add docs, tests.
* add newtypes for Tag and Reserved void pointers
* manually fix request_registered_call binding
* use nocode keyword to fix Ptr () problems
* decouple copying Slice from freeing slice
* Add time ops
* remove nocode decls
* Start Op module, fix c2hs preprocessing order
* metadata manipulation operations
* metadata free function, test
* helper functions for constructing ops of each type
* bindings for op creation functions
* finish up Op creation functions, implement Op destruction, add docs.
* tweak documentation
* rework Op creation functions to work with an array of ops, for ease of use with grpc_call_start_batch
* forgot to change return types
* wrap hook lines, fix types to op creation functions
* implement part of the payload test
* hideous, but working, end to end test
* bindings for connectivity state checks, split test into two threads
* various cleanup
* rename Core to Unsafe for emphasis, clean up tests more
* begin safe low-level facilities
* begin completion queue and server stuff
* Finish server start/stop, cq start/stop, add tests
* facilities for safely executing op batches
* reorganize LowLevel modules, begin explicit export list
* client functionality, stub payload test, various refactors
* tweak cabal file, add test
* add more documentation
* doc tweaks
* begin refactor to improve CompletionQueue safety
* export only thread-safe CQ functions, add registered call creation and other CQ utilities
* begin refactor to use GRPCIO monad, fix missing push semaphore, fix mem leak in server calls
* switch to explicit Either where needed
* add crashing tests, continue fleshing out serverHandleNormalCall
* fix haddock error, finish first draft of request handling function
* reduce GHC warnings
* non-registered client request helpers
* initial request/response test working
* don't pass tags around; generate where needed
* server call bracket functions
* correct order of semaphore acquisition and shutdown check
* simple debug flag logging, simplify Call type
* fix various registered method issues (but still not working)
* cleanup
* delete old code
* remove old todo
* use MetadataMap synonym pervasively
* more comments
* update TODOs
* tweak safety caveat
* docs tweaks
* improve haddocks
* add casts to eliminate clang warnings, remove unused function
* update options to eliminate cabal warnings
* remove outdated todo
* remove unneeded exports from CompletionQueue
* rename to GRPCIOCallError, re-add create/shutdown exports (needed for Server module)
* newtypes for hosts and method names
* more newtypes
* more debug logging
* Fix flag name collision
* instrument uses of free
* more debug
* switch to STM for completion queue stuff
* reduce warnings
* more debugging, create/destroy call tests
* refactor, fix failure cleanup for server call creation. More tests passing.
* formatting tweaks
2016-05-24 22:34:50 +02:00
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
|
|
|
|
module Network.GRPC.LowLevel.Client where
|
|
|
|
|
|
|
|
import Control.Exception (bracket, finally)
|
|
|
|
import Data.ByteString (ByteString)
|
|
|
|
import Foreign.Ptr (nullPtr)
|
|
|
|
import qualified Network.GRPC.Unsafe as C
|
|
|
|
import qualified Network.GRPC.Unsafe.Time as C
|
|
|
|
import qualified Network.GRPC.Unsafe.Constants as C
|
|
|
|
import qualified Network.GRPC.Unsafe.Op as C
|
|
|
|
|
|
|
|
import Network.GRPC.LowLevel.GRPC
|
|
|
|
import Network.GRPC.LowLevel.CompletionQueue
|
|
|
|
import Network.GRPC.LowLevel.Call
|
|
|
|
import Network.GRPC.LowLevel.Op
|
|
|
|
|
|
|
|
-- | Represents the context needed to perform client-side gRPC operations.
|
|
|
|
data Client = Client {clientChannel :: C.Channel,
|
|
|
|
clientCQ :: CompletionQueue}
|
|
|
|
|
|
|
|
-- | Configuration necessary to set up a client.
|
|
|
|
data ClientConfig = ClientConfig {clientServerHost :: Host,
|
|
|
|
clientServerPort :: Int}
|
|
|
|
|
|
|
|
createClient :: GRPC -> ClientConfig -> IO Client
|
|
|
|
createClient grpc ClientConfig{..} = do
|
|
|
|
let hostPort = (unHost clientServerHost) ++ ":" ++ (show clientServerPort)
|
|
|
|
chan <- C.grpcInsecureChannelCreate hostPort nullPtr C.reserved
|
|
|
|
cq <- createCompletionQueue grpc
|
|
|
|
return $ Client chan cq
|
|
|
|
|
|
|
|
destroyClient :: Client -> IO ()
|
|
|
|
destroyClient Client{..} = do
|
|
|
|
shutdownResult <- shutdownCompletionQueue clientCQ
|
|
|
|
case shutdownResult of
|
|
|
|
Left x -> do putStrLn $ "Failed to stop client CQ: " ++ show x
|
|
|
|
putStrLn $ "Trying to shut down anyway."
|
|
|
|
Right _ -> return ()
|
|
|
|
C.grpcChannelDestroy clientChannel
|
|
|
|
|
|
|
|
withClient :: GRPC -> ClientConfig -> (Client -> IO a) -> IO a
|
|
|
|
withClient grpc config = bracket (createClient grpc config)
|
|
|
|
(\c -> grpcDebug "withClient: destroying."
|
|
|
|
>> destroyClient c)
|
|
|
|
|
|
|
|
-- | Register a method on the client so that we can call it with
|
|
|
|
-- 'clientRegisteredRequest'.
|
|
|
|
clientRegisterMethod :: Client
|
|
|
|
-> MethodName
|
|
|
|
-- ^ method name, e.g. "/foo"
|
|
|
|
-> Host
|
|
|
|
-- ^ host name, e.g. "localhost"
|
|
|
|
-> GRPCMethodType
|
|
|
|
-> IO RegisteredMethod
|
|
|
|
clientRegisterMethod Client{..} name host Normal = do
|
|
|
|
handle <- C.grpcChannelRegisterCall clientChannel (unMethodName name)
|
|
|
|
(unHost host) C.reserved
|
|
|
|
return $ RegisteredMethod Normal name host handle
|
|
|
|
clientRegisterMethod _ _ _ _ = error "Streaming methods not yet implemented."
|
|
|
|
|
|
|
|
-- | Create a new call on the client for a registered method.
|
|
|
|
-- Returns 'Left' if the CQ is shutting down or if the job to create a call
|
|
|
|
-- timed out.
|
|
|
|
clientCreateRegisteredCall :: Client -> RegisteredMethod -> TimeoutSeconds
|
|
|
|
-> IO (Either GRPCIOError Call)
|
|
|
|
clientCreateRegisteredCall Client{..} RegisteredMethod{..} timeout = do
|
|
|
|
let parentCall = C.Call nullPtr --Unsure what this does. null is safe, though.
|
|
|
|
C.withDeadlineSeconds timeout $ \deadline -> do
|
|
|
|
channelCreateRegisteredCall clientChannel parentCall C.propagateDefaults
|
|
|
|
clientCQ methodHandle deadline
|
|
|
|
|
|
|
|
-- TODO: the error-handling refactor made this quite ugly. It could be fixed
|
|
|
|
-- by switching to ExceptT IO.
|
|
|
|
-- | Handles safe creation and cleanup of a client call
|
|
|
|
withClientRegisteredCall :: Client -> RegisteredMethod -> TimeoutSeconds
|
|
|
|
-> (Call
|
|
|
|
-> IO (Either GRPCIOError a))
|
|
|
|
-> IO (Either GRPCIOError a)
|
|
|
|
withClientRegisteredCall client regmethod timeout f = do
|
|
|
|
createResult <- clientCreateRegisteredCall client regmethod timeout
|
|
|
|
case createResult of
|
|
|
|
Left x -> return $ Left x
|
|
|
|
Right call -> f call `finally` logDestroy call
|
|
|
|
where logDestroy c = grpcDebug "withClientRegisteredCall: destroying."
|
|
|
|
>> destroyCall c
|
|
|
|
|
|
|
|
-- | Create a call on the client for an endpoint without using the
|
|
|
|
-- method registration machinery. In practice, we'll probably only use the
|
|
|
|
-- registered method version, but we include this for completeness and testing.
|
|
|
|
clientCreateCall :: Client
|
|
|
|
-> MethodName
|
|
|
|
-- ^ The method name
|
|
|
|
-> Host
|
|
|
|
-- ^ The host.
|
|
|
|
-> TimeoutSeconds
|
|
|
|
-> IO (Either GRPCIOError Call)
|
|
|
|
clientCreateCall Client{..} method host timeout = do
|
|
|
|
let parentCall = C.Call nullPtr
|
|
|
|
C.withDeadlineSeconds timeout $ \deadline -> do
|
|
|
|
channelCreateCall clientChannel parentCall C.propagateDefaults
|
|
|
|
clientCQ method host deadline
|
|
|
|
|
|
|
|
withClientCall :: Client -> MethodName -> Host -> TimeoutSeconds
|
|
|
|
-> (Call -> IO (Either GRPCIOError a))
|
|
|
|
-> IO (Either GRPCIOError a)
|
|
|
|
withClientCall client method host timeout f = do
|
|
|
|
createResult <- clientCreateCall client method host timeout
|
|
|
|
case createResult of
|
|
|
|
Left x -> return $ Left x
|
|
|
|
Right call -> f call `finally` logDestroy call
|
|
|
|
where logDestroy c = grpcDebug "withClientCall: destroying."
|
|
|
|
>> destroyCall c
|
|
|
|
|
|
|
|
data NormalRequestResult = NormalRequestResult
|
|
|
|
ByteString
|
2016-05-25 22:11:30 +02:00
|
|
|
(Maybe MetadataMap) --init metadata
|
Begin safe low-level Haskell layer (#7)
* grpc_server_request_call
* basic slice functionality
* rename function to emphasize side effects
* add docs
* ByteBuffer function bindings
* replace unsafeCoerce with more specific function, add docs, tests.
* add newtypes for Tag and Reserved void pointers
* manually fix request_registered_call binding
* use nocode keyword to fix Ptr () problems
* decouple copying Slice from freeing slice
* Add time ops
* remove nocode decls
* Start Op module, fix c2hs preprocessing order
* metadata manipulation operations
* metadata free function, test
* helper functions for constructing ops of each type
* bindings for op creation functions
* finish up Op creation functions, implement Op destruction, add docs.
* tweak documentation
* rework Op creation functions to work with an array of ops, for ease of use with grpc_call_start_batch
* forgot to change return types
* wrap hook lines, fix types to op creation functions
* implement part of the payload test
* hideous, but working, end to end test
* bindings for connectivity state checks, split test into two threads
* various cleanup
* rename Core to Unsafe for emphasis, clean up tests more
* begin safe low-level facilities
* begin completion queue and server stuff
* Finish server start/stop, cq start/stop, add tests
* facilities for safely executing op batches
* reorganize LowLevel modules, begin explicit export list
* client functionality, stub payload test, various refactors
* tweak cabal file, add test
* add more documentation
* doc tweaks
* begin refactor to improve CompletionQueue safety
* export only thread-safe CQ functions, add registered call creation and other CQ utilities
* begin refactor to use GRPCIO monad, fix missing push semaphore, fix mem leak in server calls
* switch to explicit Either where needed
* add crashing tests, continue fleshing out serverHandleNormalCall
* fix haddock error, finish first draft of request handling function
* reduce GHC warnings
* non-registered client request helpers
* initial request/response test working
* don't pass tags around; generate where needed
* server call bracket functions
* correct order of semaphore acquisition and shutdown check
* simple debug flag logging, simplify Call type
* fix various registered method issues (but still not working)
* cleanup
* delete old code
* remove old todo
* use MetadataMap synonym pervasively
* more comments
* update TODOs
* tweak safety caveat
* docs tweaks
* improve haddocks
* add casts to eliminate clang warnings, remove unused function
* update options to eliminate cabal warnings
* remove outdated todo
* remove unneeded exports from CompletionQueue
* rename to GRPCIOCallError, re-add create/shutdown exports (needed for Server module)
* newtypes for hosts and method names
* more newtypes
* more debug logging
* Fix flag name collision
* instrument uses of free
* more debug
* switch to STM for completion queue stuff
* reduce warnings
* more debugging, create/destroy call tests
* refactor, fix failure cleanup for server call creation. More tests passing.
* formatting tweaks
2016-05-24 22:34:50 +02:00
|
|
|
MetadataMap --trailing metadata
|
|
|
|
C.StatusCode
|
2016-05-25 22:11:30 +02:00
|
|
|
StatusDetails
|
Begin safe low-level Haskell layer (#7)
* grpc_server_request_call
* basic slice functionality
* rename function to emphasize side effects
* add docs
* ByteBuffer function bindings
* replace unsafeCoerce with more specific function, add docs, tests.
* add newtypes for Tag and Reserved void pointers
* manually fix request_registered_call binding
* use nocode keyword to fix Ptr () problems
* decouple copying Slice from freeing slice
* Add time ops
* remove nocode decls
* Start Op module, fix c2hs preprocessing order
* metadata manipulation operations
* metadata free function, test
* helper functions for constructing ops of each type
* bindings for op creation functions
* finish up Op creation functions, implement Op destruction, add docs.
* tweak documentation
* rework Op creation functions to work with an array of ops, for ease of use with grpc_call_start_batch
* forgot to change return types
* wrap hook lines, fix types to op creation functions
* implement part of the payload test
* hideous, but working, end to end test
* bindings for connectivity state checks, split test into two threads
* various cleanup
* rename Core to Unsafe for emphasis, clean up tests more
* begin safe low-level facilities
* begin completion queue and server stuff
* Finish server start/stop, cq start/stop, add tests
* facilities for safely executing op batches
* reorganize LowLevel modules, begin explicit export list
* client functionality, stub payload test, various refactors
* tweak cabal file, add test
* add more documentation
* doc tweaks
* begin refactor to improve CompletionQueue safety
* export only thread-safe CQ functions, add registered call creation and other CQ utilities
* begin refactor to use GRPCIO monad, fix missing push semaphore, fix mem leak in server calls
* switch to explicit Either where needed
* add crashing tests, continue fleshing out serverHandleNormalCall
* fix haddock error, finish first draft of request handling function
* reduce GHC warnings
* non-registered client request helpers
* initial request/response test working
* don't pass tags around; generate where needed
* server call bracket functions
* correct order of semaphore acquisition and shutdown check
* simple debug flag logging, simplify Call type
* fix various registered method issues (but still not working)
* cleanup
* delete old code
* remove old todo
* use MetadataMap synonym pervasively
* more comments
* update TODOs
* tweak safety caveat
* docs tweaks
* improve haddocks
* add casts to eliminate clang warnings, remove unused function
* update options to eliminate cabal warnings
* remove outdated todo
* remove unneeded exports from CompletionQueue
* rename to GRPCIOCallError, re-add create/shutdown exports (needed for Server module)
* newtypes for hosts and method names
* more newtypes
* more debug logging
* Fix flag name collision
* instrument uses of free
* more debug
* switch to STM for completion queue stuff
* reduce warnings
* more debugging, create/destroy call tests
* refactor, fix failure cleanup for server call creation. More tests passing.
* formatting tweaks
2016-05-24 22:34:50 +02:00
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
-- | Function for assembling call result when the 'MethodType' is 'Normal'.
|
|
|
|
compileNormalRequestResults :: [OpRecvResult] -> NormalRequestResult
|
|
|
|
compileNormalRequestResults
|
|
|
|
--TODO: consider using more precise type instead of match.
|
|
|
|
-- Whether we do so depends on whether this layer of abstraction is supposed
|
|
|
|
-- to be a safe interface to the gRPC C core library, or something that makes
|
|
|
|
-- core use cases easy.
|
|
|
|
[OpRecvInitialMetadataResult m,
|
|
|
|
OpRecvMessageResult body,
|
2016-05-25 22:11:30 +02:00
|
|
|
OpRecvStatusOnClientResult m2 status details]
|
|
|
|
= NormalRequestResult body (Just m) m2 status (StatusDetails details)
|
|
|
|
-- TODO: it seems registered request responses on the server
|
|
|
|
-- don't send initial metadata. Hence the 'Maybe'. Investigate.
|
|
|
|
compileNormalRequestResults
|
|
|
|
[OpRecvMessageResult body,
|
|
|
|
OpRecvStatusOnClientResult m2 status details]
|
|
|
|
= NormalRequestResult body Nothing m2 status (StatusDetails details)
|
Begin safe low-level Haskell layer (#7)
* grpc_server_request_call
* basic slice functionality
* rename function to emphasize side effects
* add docs
* ByteBuffer function bindings
* replace unsafeCoerce with more specific function, add docs, tests.
* add newtypes for Tag and Reserved void pointers
* manually fix request_registered_call binding
* use nocode keyword to fix Ptr () problems
* decouple copying Slice from freeing slice
* Add time ops
* remove nocode decls
* Start Op module, fix c2hs preprocessing order
* metadata manipulation operations
* metadata free function, test
* helper functions for constructing ops of each type
* bindings for op creation functions
* finish up Op creation functions, implement Op destruction, add docs.
* tweak documentation
* rework Op creation functions to work with an array of ops, for ease of use with grpc_call_start_batch
* forgot to change return types
* wrap hook lines, fix types to op creation functions
* implement part of the payload test
* hideous, but working, end to end test
* bindings for connectivity state checks, split test into two threads
* various cleanup
* rename Core to Unsafe for emphasis, clean up tests more
* begin safe low-level facilities
* begin completion queue and server stuff
* Finish server start/stop, cq start/stop, add tests
* facilities for safely executing op batches
* reorganize LowLevel modules, begin explicit export list
* client functionality, stub payload test, various refactors
* tweak cabal file, add test
* add more documentation
* doc tweaks
* begin refactor to improve CompletionQueue safety
* export only thread-safe CQ functions, add registered call creation and other CQ utilities
* begin refactor to use GRPCIO monad, fix missing push semaphore, fix mem leak in server calls
* switch to explicit Either where needed
* add crashing tests, continue fleshing out serverHandleNormalCall
* fix haddock error, finish first draft of request handling function
* reduce GHC warnings
* non-registered client request helpers
* initial request/response test working
* don't pass tags around; generate where needed
* server call bracket functions
* correct order of semaphore acquisition and shutdown check
* simple debug flag logging, simplify Call type
* fix various registered method issues (but still not working)
* cleanup
* delete old code
* remove old todo
* use MetadataMap synonym pervasively
* more comments
* update TODOs
* tweak safety caveat
* docs tweaks
* improve haddocks
* add casts to eliminate clang warnings, remove unused function
* update options to eliminate cabal warnings
* remove outdated todo
* remove unneeded exports from CompletionQueue
* rename to GRPCIOCallError, re-add create/shutdown exports (needed for Server module)
* newtypes for hosts and method names
* more newtypes
* more debug logging
* Fix flag name collision
* instrument uses of free
* more debug
* switch to STM for completion queue stuff
* reduce warnings
* more debugging, create/destroy call tests
* refactor, fix failure cleanup for server call creation. More tests passing.
* formatting tweaks
2016-05-24 22:34:50 +02:00
|
|
|
compileNormalRequestResults _ =
|
|
|
|
--TODO: impossible case should be enforced by more precise types.
|
|
|
|
error "non-normal request input to compileNormalRequestResults."
|
|
|
|
|
|
|
|
|
|
|
|
-- | Make a request of the given method with the given body. Returns the
|
|
|
|
-- server's response. TODO: This is preliminary until we figure out how many
|
|
|
|
-- different variations on sending request ops will be needed for full gRPC
|
|
|
|
-- functionality.
|
|
|
|
clientRegisteredRequest :: Client
|
|
|
|
-> RegisteredMethod
|
|
|
|
-> TimeoutSeconds
|
|
|
|
-- ^ Timeout of both the grpc_call and the
|
|
|
|
-- max time to wait for the completion of the batch.
|
|
|
|
-- TODO: I think we will need to decouple the
|
|
|
|
-- lifetime of the call from the queue deadline once
|
|
|
|
-- we expose functionality for streaming calls, where
|
|
|
|
-- one call object persists across many batches.
|
|
|
|
-> ByteString
|
|
|
|
-- ^ The body of the request.
|
|
|
|
-> MetadataMap
|
|
|
|
-- ^ Metadata to send with the request.
|
|
|
|
-> IO (Either GRPCIOError NormalRequestResult)
|
|
|
|
clientRegisteredRequest client@(Client{..}) rm@(RegisteredMethod{..})
|
|
|
|
timeLimit body meta =
|
|
|
|
case methodType of
|
|
|
|
Normal -> withClientRegisteredCall client rm timeLimit $ \call -> do
|
|
|
|
grpcDebug "clientRegisteredRequest: created call."
|
|
|
|
debugCall call
|
|
|
|
--TODO: doing one op at a time to debug. Some were hanging.
|
|
|
|
let op1 = [OpSendInitialMetadata meta]
|
|
|
|
res1 <- runOps call clientCQ op1 timeLimit
|
|
|
|
grpcDebug $ "finished res1: " ++ show res1
|
|
|
|
let op2 = [OpSendMessage body]
|
|
|
|
res2 <- runOps call clientCQ op2 timeLimit
|
|
|
|
grpcDebug $ "finished res2: " ++ show res2
|
|
|
|
let op3 = [OpSendCloseFromClient]
|
|
|
|
res3 <- runOps call clientCQ op3 timeLimit
|
|
|
|
grpcDebug $ "finished res3: " ++ show res3
|
|
|
|
let op4 = [OpRecvMessage]
|
|
|
|
res4 <- runOps call clientCQ op4 timeLimit
|
|
|
|
grpcDebug $ "finished res4: " ++ show res4
|
|
|
|
let op5 = [OpRecvStatusOnClient]
|
|
|
|
res5 <- runOps call clientCQ op5 timeLimit
|
|
|
|
grpcDebug $ "finished res5: " ++ show res5
|
|
|
|
let results = do
|
|
|
|
r1 <- res1
|
|
|
|
r2 <- res2
|
|
|
|
r3 <- res3
|
|
|
|
r4 <- res4
|
|
|
|
r5 <- res5
|
|
|
|
return $ r1 ++ r2 ++ r3 ++ r4 ++ r5
|
|
|
|
case results of
|
|
|
|
Left x -> return $ Left x
|
|
|
|
Right rs -> return $
|
|
|
|
Right $ compileNormalRequestResults rs
|
|
|
|
_ -> error "Streaming methods not yet implemented."
|
|
|
|
|
|
|
|
-- | Makes a normal (non-streaming) request without needing to register a method
|
|
|
|
-- first. Probably only useful for testing. TODO: This is preliminary, like
|
|
|
|
-- 'clientRegisteredRequest'.
|
|
|
|
clientRequest :: Client
|
|
|
|
-> MethodName
|
|
|
|
-- ^ Method name, e.g. "/foo"
|
|
|
|
-> Host
|
|
|
|
-- ^ Host. Not sure if used.
|
|
|
|
-> TimeoutSeconds
|
|
|
|
-> ByteString
|
|
|
|
-- ^ Request body.
|
|
|
|
-> MetadataMap
|
|
|
|
-- ^ Request metadata.
|
|
|
|
-> IO (Either GRPCIOError NormalRequestResult)
|
|
|
|
clientRequest client@(Client{..}) (MethodName method) (Host host)
|
|
|
|
timeLimit body meta = do
|
|
|
|
withClientCall client (MethodName method) (Host host) timeLimit $ \call -> do
|
|
|
|
let ops = clientNormalRequestOps body meta
|
|
|
|
results <- runOps call clientCQ ops timeLimit
|
|
|
|
grpcDebug "clientRequest: ops ran."
|
|
|
|
case results of
|
|
|
|
Left x -> return $ Left x
|
|
|
|
Right rs -> return $ Right $ compileNormalRequestResults rs
|
|
|
|
|
|
|
|
|
|
|
|
clientNormalRequestOps :: ByteString -> MetadataMap -> [Op]
|
|
|
|
clientNormalRequestOps body metadata =
|
|
|
|
[OpSendInitialMetadata metadata,
|
|
|
|
OpSendMessage body,
|
|
|
|
OpSendCloseFromClient,
|
|
|
|
OpRecvInitialMetadata,
|
|
|
|
OpRecvMessage,
|
|
|
|
OpRecvStatusOnClient]
|