server/Thrift/Server/CppServer.hs (122 lines of code) (raw):
-- Copyright (c) Facebook, Inc. and its affiliates.
{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}
module Thrift.Server.CppServer
( Server(..)
, withBackgroundServer
, module Thrift.Server.Types
) where
import Control.Concurrent.Async
import Control.Concurrent.MVar
import Control.Exception
import Control.Monad
import Data.Maybe
#if __GLASGOW_HASKELL == 804
import Data.Monoid ((<>))
#endif
import Data.Proxy
import Data.Text (Text)
import Foreign
import Foreign.C
import Foreign.CPP.Marshallable.TH
import Foreign.CPP.HsStruct
import GHC.Event (Lifetime(OneShot))
import System.Posix.Types
import Util.Fd
import Util.Text
import Thrift.Server.ProcessorCallback
import Thrift.Server.Types
import Thrift.Processor
-- -----------------------------------------------------------------------------
-- Server data types
data CServer
type PServer = Ptr CServer
$(deriveDestructibleUnsafe "CreateCppServerResult" [t| HsEither PServer HsText |])
data Server = Server
{ pServer :: PServer
, serverPort :: Int
}
-- -----------------------------------------------------------------------------
-- Down to business
-- | Spawns a background thread that blocks on the server
-- Creates a server in a background thread to serve requests. After the server
-- has been successfully started, the supplied Server -> IO a action is
-- executed, and when this action returns the server is terminated. A pool of
-- worker threads executes requests by calling the s r -> IO r action for each
-- request received.
withBackgroundServer :: forall s a . (Processor s)
=> (forall r . s r -> IO r) -- ^ handler to use
-> ServerOptions
-> (Server -> IO a)
-> IO a
withBackgroundServer handler ServerOptions{..} action =
withProcessorCallback handler runServer
where
err = ServerException "failed to get event manager"
cPort = fromIntegral $ fromMaybe 0 desiredPort
cNumWorkers = fromIntegral $ fromMaybe 0 numWorkerThreads
-- Use the normal factory unless a custom was handed to us
factoryFn = fromMaybe c_haskell_factory customFactoryFn
-- Function to modify the ThriftServer
modifyFn = fromMaybe nullFunPtr customModifyFn
-- Set the instance to pull oneway functions from
oneways = onewayFns (undefined :: Proxy s)
throwEx prefix = throwIO . ServerException . ((prefix <> ": ") <>)
-- Creates a PServer to run `act` on
withCServer cb act =
useTextsAsCStringLens oneways $ \txts sizes txtlen -> do
let
alloc = bracket
(create_cpp_server cb factoryFn cPort
cNumWorkers txts sizes txtlen)
delete
$ \p -> do
(r :: Either PServer Text) <- coerce $ peek p
case r of
Left ps -> return ps
Right exStr -> throwEx "create_cpp_server" exStr
bracket alloc destroy_cpp_server act
runServer cb =
withCServer cb $ \ps ->
alloca $ \portPtr -> do
mvar <- newEmptyMVar
let callback = putMVar mvar =<< peek portPtr
withFdEventNotification err callback OneShot $ \(Fd cfd) ->
let
start =
bracket (c_serve ps cfd portPtr modifyFn) delete $ \ptr ->
when (ptr /= nullPtr) $ do
(exStr :: Text) <- coerce <$> peek ptr
throwEx "cpp_server" exStr
stop a = c_stop ps >> wait a
in
bracket (async start) stop $ \_ -> do
port <- takeMVar mvar -- Blocks until serving or exception
when (port == 0) (throwEx "cpp_server" "this should never happen")
action Server
{ pServer = ps
, serverPort = fromIntegral port
}
-- -----------------------------------------------------------------------------
-- FFI
foreign import ccall safe "c_create_cpp_server"
create_cpp_server :: FunPtr ProcessorCallback
-> FactoryFunction
-> CInt -- ^ port
-> CInt -- ^ workers, or 0 to use the default
-> Ptr CString -- ^ oneway function names array
-> Ptr CSize -- ^ oneway function name lengths array
-> CSize -- ^ number of oneway functions
-> IO (Ptr (HsEither PServer HsText))
foreign import ccall safe "c_destroy_cpp_server"
destroy_cpp_server :: PServer -> IO ()
foreign import ccall safe "c_serve_cpp_server"
c_serve :: PServer
-> CInt -- ^ file descriptor to write to
-> Ptr CInt -- ^ place to put value of port
-> ModifyFunction
-> IO (Ptr HsText)
foreign import ccall safe "c_stop_cpp_server"
c_stop :: PServer -> IO ()
foreign import ccall "&c_haskell_factory"
c_haskell_factory :: FactoryFunction