|
|
|
@ -1,4 +1,5 @@ |
|
|
|
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
|
|
|
|
{-# LANGUAGE QuasiQuotes #-} |
|
|
|
|
|
|
|
|
|
|
|
module ATrade.Broker.Server ( |
|
|
|
module ATrade.Broker.Server ( |
|
|
|
startBrokerServer, |
|
|
|
startBrokerServer, |
|
|
|
@ -15,18 +16,19 @@ import ATrade.Broker.Protocol (BrokerServerRequest (..), |
|
|
|
ClientIdentity, |
|
|
|
ClientIdentity, |
|
|
|
Notification (..), |
|
|
|
Notification (..), |
|
|
|
NotificationSqnum (NotificationSqnum), |
|
|
|
NotificationSqnum (NotificationSqnum), |
|
|
|
RequestSqnum, |
|
|
|
RequestId (..), |
|
|
|
getNotificationSqnum, |
|
|
|
getNotificationSqnum, |
|
|
|
nextSqnum, requestSqnum) |
|
|
|
getRequestId, nextSqnum) |
|
|
|
import ATrade.Logging (Message (Message), |
|
|
|
import ATrade.Logging (Message (Message), |
|
|
|
Severity (Debug, Warning), |
|
|
|
Severity (Debug, Warning), |
|
|
|
logWith) |
|
|
|
Severity (Info), logWith) |
|
|
|
import ATrade.Logging (Severity (Info)) |
|
|
|
|
|
|
|
import ATrade.Types (Order (orderAccountId, orderId), |
|
|
|
import ATrade.Types (Order (orderAccountId, orderId), |
|
|
|
OrderId, |
|
|
|
OrderId, |
|
|
|
ServerSecurityParams (sspCertificate, sspDomain), |
|
|
|
ServerSecurityParams (sspCertificate, sspDomain), |
|
|
|
Trade (tradeOrderId)) |
|
|
|
Trade (tradeOrderId)) |
|
|
|
import ATrade.Util (atomicMapIORef) |
|
|
|
import ATrade.Util (atomicMapIORef) |
|
|
|
|
|
|
|
import ATrade.Utils.MessagePipe (emptyMessagePipe, getMessages, |
|
|
|
|
|
|
|
push) |
|
|
|
import Colog (LogAction) |
|
|
|
import Colog (LogAction) |
|
|
|
import Control.Concurrent (MVar, ThreadId, forkIO, |
|
|
|
import Control.Concurrent (MVar, ThreadId, forkIO, |
|
|
|
killThread, myThreadId, |
|
|
|
killThread, myThreadId, |
|
|
|
@ -34,107 +36,89 @@ import Control.Concurrent (MVar, ThreadId, forkIO, |
|
|
|
readMVar, threadDelay, |
|
|
|
readMVar, threadDelay, |
|
|
|
tryReadMVar, yield) |
|
|
|
tryReadMVar, yield) |
|
|
|
import Control.Concurrent.BoundedChan (BoundedChan, newBoundedChan, |
|
|
|
import Control.Concurrent.BoundedChan (BoundedChan, newBoundedChan, |
|
|
|
tryReadChan, tryWriteChan) |
|
|
|
readChan, tryReadChan, |
|
|
|
import Control.Exception (finally) |
|
|
|
tryWriteChan) |
|
|
|
import Control.Monad (unless) |
|
|
|
import Control.Exception (bracket, finally) |
|
|
|
|
|
|
|
import Control.Monad (unless, void, when) |
|
|
|
|
|
|
|
import Control.Monad.Extra (forever) |
|
|
|
import Control.Monad.Loops (whileM_) |
|
|
|
import Control.Monad.Loops (whileM_) |
|
|
|
import Data.Aeson (eitherDecode, encode) |
|
|
|
import Data.Aeson (eitherDecode, encode) |
|
|
|
import qualified Data.Bimap as BM |
|
|
|
import qualified Data.Bimap as BM |
|
|
|
import qualified Data.ByteString as B hiding (putStrLn) |
|
|
|
import qualified Data.ByteString as B hiding (putStrLn) |
|
|
|
import qualified Data.ByteString.Lazy as BL hiding (putStrLn) |
|
|
|
import qualified Data.ByteString.Lazy as BL hiding (putStrLn) |
|
|
|
import Data.IORef (IORef, atomicModifyIORef', |
|
|
|
import Data.IORef (IORef, atomicModifyIORef', |
|
|
|
newIORef, readIORef) |
|
|
|
newIORef, readIORef, |
|
|
|
|
|
|
|
writeIORef) |
|
|
|
import qualified Data.List as L |
|
|
|
import qualified Data.List as L |
|
|
|
import Data.List.NonEmpty (NonEmpty ((:|))) |
|
|
|
import Data.List.NonEmpty (NonEmpty ((:|))) |
|
|
|
import qualified Data.Map as M |
|
|
|
import qualified Data.Map as M |
|
|
|
import Data.Maybe (isJust, isNothing) |
|
|
|
import Data.Maybe (isJust, isNothing) |
|
|
|
import qualified Data.Text as T |
|
|
|
import qualified Data.Text as T |
|
|
|
import qualified Data.Text.Encoding as E |
|
|
|
import qualified Data.Text.Encoding as E |
|
|
|
|
|
|
|
import qualified Data.Text.Lazy as TL |
|
|
|
import Data.Time.Clock () |
|
|
|
import Data.Time.Clock () |
|
|
|
import Safe (lastMay) |
|
|
|
import Language.Haskell.Printf |
|
|
|
|
|
|
|
import Network.Socket (Family (AF_INET), |
|
|
|
|
|
|
|
SockAddr (SockAddrInet), |
|
|
|
|
|
|
|
Socket, SocketType (Stream), |
|
|
|
|
|
|
|
accept, bind, defaultProtocol, |
|
|
|
|
|
|
|
listen, socket) |
|
|
|
|
|
|
|
import Network.Socket.ByteString (recv, sendAll) |
|
|
|
|
|
|
|
import Safe (lastMay, readMay) |
|
|
|
import System.Timeout () |
|
|
|
import System.Timeout () |
|
|
|
import System.ZMQ4 (Context, Event (In), |
|
|
|
import System.ZMQ4 hiding (Socket, Stream, bind, |
|
|
|
Poll (Sock), Pub (..), |
|
|
|
socket) |
|
|
|
Router (..), Socket, |
|
|
|
|
|
|
|
Switch (On), bind, close, poll, |
|
|
|
|
|
|
|
receiveMulti, restrict, |
|
|
|
|
|
|
|
sendMulti, setCurveServer, |
|
|
|
|
|
|
|
setLinger, setTcpKeepAlive, |
|
|
|
|
|
|
|
setTcpKeepAliveCount, |
|
|
|
|
|
|
|
setTcpKeepAliveIdle, |
|
|
|
|
|
|
|
setTcpKeepAliveInterval, |
|
|
|
|
|
|
|
setZapDomain, socket) |
|
|
|
|
|
|
|
import System.ZMQ4.ZAP (zapApplyCertificate) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type PeerId = B.ByteString |
|
|
|
type PeerId = B.ByteString |
|
|
|
|
|
|
|
|
|
|
|
data FullOrderId = FullOrderId ClientIdentity OrderId |
|
|
|
data FullOrderId = FullOrderId ClientIdentity OrderId |
|
|
|
deriving (Show, Eq, Ord) |
|
|
|
deriving (Show, Eq, Ord) |
|
|
|
|
|
|
|
|
|
|
|
data BrokerServerState = BrokerServerState { |
|
|
|
data ClientState = ClientState { |
|
|
|
bsSocket :: Socket Router, |
|
|
|
cThreadId :: ThreadId, |
|
|
|
bsNotificationsSocket :: Socket Pub, |
|
|
|
cSocket :: Socket, |
|
|
|
orderToBroker :: M.Map FullOrderId BrokerBackend, |
|
|
|
cClientIdentity :: ClientIdentity, |
|
|
|
orderMap :: BM.Bimap FullOrderId OrderId, |
|
|
|
cEgressQueue :: BoundedChan B.ByteString |
|
|
|
lastPacket :: M.Map PeerId (RequestSqnum, BrokerServerResponse), |
|
|
|
} |
|
|
|
pendingNotifications :: M.Map ClientIdentity [Notification], |
|
|
|
|
|
|
|
notificationSqnum :: M.Map ClientIdentity NotificationSqnum, |
|
|
|
|
|
|
|
brokers :: [BrokerBackend], |
|
|
|
|
|
|
|
completionMvar :: MVar (), |
|
|
|
|
|
|
|
killMvar :: MVar (), |
|
|
|
|
|
|
|
orderIdCounter :: OrderId, |
|
|
|
|
|
|
|
tradeSink :: BoundedChan Trade, |
|
|
|
|
|
|
|
initialSqnum :: NotificationSqnum |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data BrokerServerState = BrokerServerState { |
|
|
|
|
|
|
|
orderToBroker :: M.Map FullOrderId BrokerBackend, |
|
|
|
|
|
|
|
orderMap :: BM.Bimap FullOrderId OrderId, |
|
|
|
|
|
|
|
pendingNotifications :: M.Map ClientIdentity [Notification], |
|
|
|
|
|
|
|
notificationSqnum :: M.Map ClientIdentity NotificationSqnum, |
|
|
|
|
|
|
|
brokers :: [BrokerBackend], |
|
|
|
|
|
|
|
completionMvar :: MVar (), |
|
|
|
|
|
|
|
killMvar :: MVar (), |
|
|
|
|
|
|
|
orderIdCounter :: OrderId, |
|
|
|
|
|
|
|
tradeSink :: BoundedChan Trade, |
|
|
|
|
|
|
|
initialSqnum :: NotificationSqnum |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
data BrokerServerHandle = BrokerServerHandle ThreadId ThreadId (MVar ()) (MVar ()) |
|
|
|
data BrokerServerHandle = BrokerServerHandle |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
bhServerTid :: ThreadId |
|
|
|
|
|
|
|
, bhClients :: IORef (M.Map ClientIdentity ClientState) |
|
|
|
|
|
|
|
, bhKillMVar :: MVar () |
|
|
|
|
|
|
|
, bhCompletionMVar :: MVar () |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type TradeSink = Trade -> IO () |
|
|
|
type TradeSink = Trade -> IO () |
|
|
|
|
|
|
|
|
|
|
|
startBrokerServer :: [BrokerBackend] -> |
|
|
|
startBrokerServer :: [BrokerBackend] -> |
|
|
|
Context -> |
|
|
|
Context -> |
|
|
|
T.Text -> |
|
|
|
T.Text -> |
|
|
|
T.Text -> |
|
|
|
|
|
|
|
NotificationSqnum -> |
|
|
|
NotificationSqnum -> |
|
|
|
[TradeSink] -> |
|
|
|
[TradeSink] -> |
|
|
|
ServerSecurityParams -> |
|
|
|
|
|
|
|
LogAction IO Message -> |
|
|
|
LogAction IO Message -> |
|
|
|
IO BrokerServerHandle |
|
|
|
IO BrokerServerHandle |
|
|
|
startBrokerServer brokers c ep notificationsEp initialSqnum tradeSinks params logger = do |
|
|
|
startBrokerServer brokers c ep initialSqnum tradeSinks logger = do |
|
|
|
sock <- socket c Router |
|
|
|
|
|
|
|
notificationsSock <- socket c Pub |
|
|
|
|
|
|
|
setLinger (restrict 0) sock |
|
|
|
|
|
|
|
setLinger (restrict 0) notificationsSock |
|
|
|
|
|
|
|
case sspDomain params of |
|
|
|
|
|
|
|
Just domain -> do |
|
|
|
|
|
|
|
setZapDomain (restrict $ E.encodeUtf8 domain) sock |
|
|
|
|
|
|
|
setZapDomain (restrict $ E.encodeUtf8 domain) notificationsSock |
|
|
|
|
|
|
|
Nothing -> return () |
|
|
|
|
|
|
|
case sspCertificate params of |
|
|
|
|
|
|
|
Just cert -> do |
|
|
|
|
|
|
|
setCurveServer True sock |
|
|
|
|
|
|
|
zapApplyCertificate cert sock |
|
|
|
|
|
|
|
setCurveServer True notificationsSock |
|
|
|
|
|
|
|
zapApplyCertificate cert notificationsSock |
|
|
|
|
|
|
|
Nothing -> return () |
|
|
|
|
|
|
|
bind sock (T.unpack ep) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setTcpKeepAlive On notificationsSock |
|
|
|
|
|
|
|
setTcpKeepAliveCount (restrict 5) notificationsSock |
|
|
|
|
|
|
|
setTcpKeepAliveIdle (restrict 60) notificationsSock |
|
|
|
|
|
|
|
setTcpKeepAliveInterval (restrict 10) notificationsSock |
|
|
|
|
|
|
|
bind notificationsSock (T.unpack notificationsEp) |
|
|
|
|
|
|
|
tid <- myThreadId |
|
|
|
|
|
|
|
compMv <- newEmptyMVar |
|
|
|
compMv <- newEmptyMVar |
|
|
|
killMv <- newEmptyMVar |
|
|
|
killMv <- newEmptyMVar |
|
|
|
tsChan <- newBoundedChan 100 |
|
|
|
tsChan <- newBoundedChan 100 |
|
|
|
|
|
|
|
clientsMapRef <- newIORef M.empty |
|
|
|
state <- newIORef BrokerServerState { |
|
|
|
state <- newIORef BrokerServerState { |
|
|
|
bsSocket = sock, |
|
|
|
|
|
|
|
bsNotificationsSocket = notificationsSock, |
|
|
|
|
|
|
|
orderMap = BM.empty, |
|
|
|
orderMap = BM.empty, |
|
|
|
orderToBroker = M.empty, |
|
|
|
orderToBroker = M.empty, |
|
|
|
lastPacket = M.empty, |
|
|
|
|
|
|
|
pendingNotifications = M.empty, |
|
|
|
pendingNotifications = M.empty, |
|
|
|
notificationSqnum = M.empty, |
|
|
|
notificationSqnum = M.empty, |
|
|
|
brokers = brokers, |
|
|
|
brokers = brokers, |
|
|
|
@ -144,18 +128,45 @@ startBrokerServer brokers c ep notificationsEp initialSqnum tradeSinks params lo |
|
|
|
tradeSink = tsChan, |
|
|
|
tradeSink = tsChan, |
|
|
|
initialSqnum = initialSqnum |
|
|
|
initialSqnum = initialSqnum |
|
|
|
} |
|
|
|
} |
|
|
|
mapM_ (\bro -> setNotificationCallback bro (Just $ notificationCallback state logger)) brokers |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log Info "Broker.Server" "Forking broker server thread" |
|
|
|
let Just (_, port) = parseHostAndPort ep |
|
|
|
BrokerServerHandle <$> forkIO (brokerServerThread state logger) <*> forkIO (tradeSinkHandler c state tradeSinks) <*> pure compMv <*> pure killMv |
|
|
|
serverSocket <- socket AF_INET Stream defaultProtocol |
|
|
|
|
|
|
|
bind serverSocket $ SockAddrInet (fromIntegral port) 0 |
|
|
|
|
|
|
|
log Info "Broker.Server" $ TL.toStrict $ [t|Listening on port %?|] $ fromIntegral port |
|
|
|
|
|
|
|
listen serverSocket 1024 |
|
|
|
|
|
|
|
serverTid <- forkIO $ forever $ do |
|
|
|
|
|
|
|
(client, addr) <- accept serverSocket |
|
|
|
|
|
|
|
log Debug "Broker.Server" "Incoming connection" |
|
|
|
|
|
|
|
rawRequest <- recv client 4096 |
|
|
|
|
|
|
|
case eitherDecode $ BL.fromStrict $ B.init rawRequest of |
|
|
|
|
|
|
|
Left err -> log Warning "Broker.Server" $ "Unable to decode client id: " <> (T.pack . show) rawRequest |
|
|
|
|
|
|
|
Right (RequestSetClientIdentity requestId clientIdentity) -> do |
|
|
|
|
|
|
|
log Info "Broker.Server" $ "Connected socket identity: " <> (T.pack . show) clientIdentity |
|
|
|
|
|
|
|
egressQueue <- newBoundedChan 100 |
|
|
|
|
|
|
|
clientTid <- forkIO $ clientThread client egressQueue clientsMapRef state logger |
|
|
|
|
|
|
|
let clientState = ClientState clientTid client clientIdentity egressQueue |
|
|
|
|
|
|
|
atomicModifyIORef' clientsMapRef (\m -> (M.insert clientIdentity clientState m, ())) |
|
|
|
|
|
|
|
_ -> log Warning "Broker.Server" $ "Invalid first message: " <> (T.pack . show) rawRequest |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mapM_ (\bro -> setNotificationCallback bro (Just $ notificationCallback state clientsMapRef logger)) brokers |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pure $ BrokerServerHandle serverTid clientsMapRef killMv compMv |
|
|
|
where |
|
|
|
where |
|
|
|
log = logWith logger |
|
|
|
log = logWith logger |
|
|
|
|
|
|
|
parseHostAndPort :: T.Text -> Maybe (T.Text, Int) |
|
|
|
|
|
|
|
parseHostAndPort str = case T.splitOn ":" str of |
|
|
|
|
|
|
|
[host, port] -> |
|
|
|
|
|
|
|
case readMay $ T.unpack port of |
|
|
|
|
|
|
|
Just numPort -> Just (host, numPort) |
|
|
|
|
|
|
|
_ -> Nothing |
|
|
|
|
|
|
|
_ -> Nothing |
|
|
|
|
|
|
|
|
|
|
|
notificationCallback :: IORef BrokerServerState -> |
|
|
|
notificationCallback :: IORef BrokerServerState -> |
|
|
|
|
|
|
|
IORef (M.Map ClientIdentity ClientState) -> |
|
|
|
LogAction IO Message -> |
|
|
|
LogAction IO Message -> |
|
|
|
BrokerBackendNotification -> |
|
|
|
BrokerBackendNotification -> |
|
|
|
IO () |
|
|
|
IO () |
|
|
|
notificationCallback state logger n = do |
|
|
|
notificationCallback state clientsMapRef logger n = do |
|
|
|
log Debug "Broker.Server" $ "Notification: " <> (T.pack . show) n |
|
|
|
log Debug "Broker.Server" $ "Notification: " <> (T.pack . show) n |
|
|
|
chan <- tradeSink <$> readIORef state |
|
|
|
chan <- tradeSink <$> readIORef state |
|
|
|
case n of |
|
|
|
case n of |
|
|
|
@ -180,8 +191,10 @@ notificationCallback state logger n = do |
|
|
|
case M.lookup clientIdentity . pendingNotifications $ s of |
|
|
|
case M.lookup clientIdentity . pendingNotifications $ s of |
|
|
|
Just ns -> s { pendingNotifications = M.insert clientIdentity (n : ns) (pendingNotifications s)} |
|
|
|
Just ns -> s { pendingNotifications = M.insert clientIdentity (n : ns) (pendingNotifications s)} |
|
|
|
Nothing -> s { pendingNotifications = M.insert clientIdentity [n] (pendingNotifications s)}) |
|
|
|
Nothing -> s { pendingNotifications = M.insert clientIdentity [n] (pendingNotifications s)}) |
|
|
|
sock <- bsNotificationsSocket <$> readIORef state |
|
|
|
clients <- readIORef clientsMapRef |
|
|
|
sendMulti sock (E.encodeUtf8 clientIdentity :| [BL.toStrict $ encode n]) |
|
|
|
case M.lookup clientIdentity clients of |
|
|
|
|
|
|
|
Just client -> void $ tryWriteChan (cEgressQueue client) $ BL.toStrict $ encode n |
|
|
|
|
|
|
|
Nothing -> log Warning "Broker.Server" $ TL.toStrict $ [t|Unable to send notification to %?|] clientIdentity |
|
|
|
|
|
|
|
|
|
|
|
tradeSinkHandler :: Context -> IORef BrokerServerState -> [TradeSink] -> IO () |
|
|
|
tradeSinkHandler :: Context -> IORef BrokerServerState -> [TradeSink] -> IO () |
|
|
|
tradeSinkHandler c state tradeSinks = unless (null tradeSinks) $ |
|
|
|
tradeSinkHandler c state tradeSinks = unless (null tradeSinks) $ |
|
|
|
@ -195,118 +208,108 @@ tradeSinkHandler c state tradeSinks = unless (null tradeSinks) $ |
|
|
|
wasKilled = isJust <$> (readIORef state >>= tryReadMVar . killMvar) |
|
|
|
wasKilled = isJust <$> (readIORef state >>= tryReadMVar . killMvar) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
brokerServerThread :: IORef BrokerServerState -> |
|
|
|
clientThread :: Socket -> |
|
|
|
LogAction IO Message -> |
|
|
|
BoundedChan B.ByteString -> |
|
|
|
IO () |
|
|
|
IORef (M.Map ClientIdentity ClientState) -> |
|
|
|
brokerServerThread state logger = finally brokerServerThread' cleanup |
|
|
|
IORef BrokerServerState -> |
|
|
|
|
|
|
|
LogAction IO Message -> |
|
|
|
|
|
|
|
IO () |
|
|
|
|
|
|
|
clientThread socket egressQueue clients serverState logger = |
|
|
|
|
|
|
|
bracket |
|
|
|
|
|
|
|
(forkIO sendingThread) |
|
|
|
|
|
|
|
(\tid -> do |
|
|
|
|
|
|
|
log Debug "Broker.Server" "Killing sending thread" |
|
|
|
|
|
|
|
killThread tid) |
|
|
|
|
|
|
|
brokerServerThread' |
|
|
|
where |
|
|
|
where |
|
|
|
log = logWith logger |
|
|
|
log = logWith logger |
|
|
|
brokerServerThread' = whileM_ (fmap killMvar (readIORef state) >>= fmap isNothing . tryReadMVar) $ do |
|
|
|
brokerServerThread' _ = do |
|
|
|
sock <- bsSocket <$> readIORef state |
|
|
|
pipeRef <- newIORef emptyMessagePipe |
|
|
|
events <- poll 100 [Sock sock [In] Nothing] |
|
|
|
brokerServerThread'' pipeRef |
|
|
|
unless (null . L.head $ events) $ do |
|
|
|
log Info "Broker.Server" "Client disconnected" |
|
|
|
msg <- receiveMulti sock |
|
|
|
|
|
|
|
case msg of |
|
|
|
brokerServerThread'' pipeRef = do |
|
|
|
[peerId, _, payload] -> do |
|
|
|
rawData <- recv socket 4096 |
|
|
|
case eitherDecode . BL.fromStrict $ payload of |
|
|
|
when (B.length rawData > 0) $ do |
|
|
|
Right request -> do |
|
|
|
pipe <- readIORef pipeRef |
|
|
|
let sqnum = requestSqnum request |
|
|
|
let (pipe', chunks) = getMessages (push rawData pipe) |
|
|
|
-- Here, we should check if previous packet sequence number is the same |
|
|
|
writeIORef pipeRef pipe' |
|
|
|
-- If it is, we should resend previous response |
|
|
|
mapM_ (handleChunk egressQueue) chunks |
|
|
|
lastPackMap <- lastPacket <$> readIORef state |
|
|
|
brokerServerThread'' pipeRef |
|
|
|
case shouldResend sqnum peerId lastPackMap of |
|
|
|
|
|
|
|
Just response -> do |
|
|
|
|
|
|
|
log Debug "Broker.Server" $ "Resending packet for peerId: " <> (T.pack . show) peerId |
|
|
|
|
|
|
|
sendMessage sock peerId response -- Resend |
|
|
|
|
|
|
|
atomicMapIORef state (\s -> s { lastPacket = M.delete peerId (lastPacket s)}) |
|
|
|
|
|
|
|
Nothing -> do |
|
|
|
|
|
|
|
-- Handle incoming request, send response |
|
|
|
|
|
|
|
response <- handleMessage peerId request |
|
|
|
|
|
|
|
sendMessage sock peerId response |
|
|
|
|
|
|
|
-- and store response in case we'll need to resend it |
|
|
|
|
|
|
|
atomicMapIORef state (\s -> s { lastPacket = M.insert peerId (sqnum, response) (lastPacket s)}) |
|
|
|
|
|
|
|
Left errmsg -> do |
|
|
|
|
|
|
|
-- If we weren't able to parse request, we should send error |
|
|
|
|
|
|
|
-- but shouldn't update lastPacket |
|
|
|
|
|
|
|
let response = ResponseError $ "Invalid request: " <> T.pack errmsg |
|
|
|
|
|
|
|
sendMessage sock peerId response |
|
|
|
|
|
|
|
_ -> log Warning "Broker.Server" ("Invalid packet received: " <> (T.pack . show) msg) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shouldResend sqnum peerId lastPackMap = case M.lookup peerId lastPackMap of |
|
|
|
sendingThread = forever $ do |
|
|
|
Just (lastSqnum, response) -> if sqnum == lastSqnum |
|
|
|
packet <- readChan egressQueue |
|
|
|
then Just response |
|
|
|
log Debug "Broker.Server" $ TL.toStrict $ [t|Sending packet: %?|] packet |
|
|
|
else Nothing |
|
|
|
sendAll socket $ B.snoc packet 0 |
|
|
|
Nothing -> Nothing |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cleanup = do |
|
|
|
enqueueEgressPacket = tryWriteChan egressQueue |
|
|
|
sock <- bsSocket <$> readIORef state |
|
|
|
|
|
|
|
close sock |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mv <- completionMvar <$> readIORef state |
|
|
|
handleChunk egressQueue payload = do |
|
|
|
putMVar mv () |
|
|
|
response <- case eitherDecode . BL.fromStrict $ payload of |
|
|
|
|
|
|
|
Right request -> handleMessage request |
|
|
|
|
|
|
|
Left errmsg -> pure $ ResponseError (RequestId 0) $ "Invalid request: " <> T.pack errmsg |
|
|
|
|
|
|
|
enqueueEgressPacket $ BL.toStrict $ encode response |
|
|
|
|
|
|
|
|
|
|
|
handleMessage :: PeerId -> BrokerServerRequest -> IO BrokerServerResponse |
|
|
|
handleMessage :: BrokerServerRequest -> IO BrokerServerResponse |
|
|
|
handleMessage peerId request = do |
|
|
|
handleMessage request = do |
|
|
|
bros <- brokers <$> readIORef state |
|
|
|
log Debug "Broker.Server" "Handle message" |
|
|
|
|
|
|
|
bros <- brokers <$> readIORef serverState |
|
|
|
case request of |
|
|
|
case request of |
|
|
|
RequestSubmitOrder sqnum clientIdentity order -> do |
|
|
|
RequestSubmitOrder requestId clientIdentity order -> do |
|
|
|
log Debug "Broker.Server" $ "Request: submit order:" <> (T.pack . show) request |
|
|
|
log Debug "Broker.Server" $ "Request: submit order:" <> (T.pack . show) request |
|
|
|
case findBrokerForAccount (orderAccountId order) bros of |
|
|
|
case findBrokerForAccount (orderAccountId order) bros of |
|
|
|
Just bro -> do |
|
|
|
Just bro -> do |
|
|
|
globalOrderId <- nextOrderId |
|
|
|
globalOrderId <- nextOrderId |
|
|
|
let fullOrderId = FullOrderId clientIdentity (orderId order) |
|
|
|
let fullOrderId = FullOrderId clientIdentity (orderId order) |
|
|
|
atomicMapIORef state (\s -> s { |
|
|
|
atomicMapIORef serverState (\s -> s { |
|
|
|
orderToBroker = M.insert fullOrderId bro (orderToBroker s), |
|
|
|
orderToBroker = M.insert fullOrderId bro (orderToBroker s), |
|
|
|
orderMap = BM.insert fullOrderId globalOrderId (orderMap s) }) |
|
|
|
orderMap = BM.insert fullOrderId globalOrderId (orderMap s) }) |
|
|
|
submitOrder bro order { orderId = globalOrderId } |
|
|
|
submitOrder bro order { orderId = globalOrderId } |
|
|
|
return ResponseOk |
|
|
|
return $ ResponseOk requestId |
|
|
|
|
|
|
|
|
|
|
|
Nothing -> do |
|
|
|
Nothing -> do |
|
|
|
log Warning "Broker.Server" $ "Unknown account: " <> (orderAccountId order) |
|
|
|
log Warning "Broker.Server" $ "Unknown account: " <> orderAccountId order |
|
|
|
return $ ResponseError "Unknown account" |
|
|
|
return $ ResponseError requestId "Unknown account" |
|
|
|
RequestCancelOrder sqnum clientIdentity localOrderId -> do |
|
|
|
RequestCancelOrder requestId clientIdentity localOrderId -> do |
|
|
|
log Debug "Broker.Server" $ "Request: cancel order:" <> (T.pack . show) request |
|
|
|
log Debug "Broker.Server" $ "Request: cancel order:" <> (T.pack . show) request |
|
|
|
m <- orderToBroker <$> readIORef state |
|
|
|
m <- orderToBroker <$> readIORef serverState |
|
|
|
bm <- orderMap <$> readIORef state |
|
|
|
bm <- orderMap <$> readIORef serverState |
|
|
|
let fullOrderId = FullOrderId clientIdentity localOrderId |
|
|
|
let fullOrderId = FullOrderId clientIdentity localOrderId |
|
|
|
case (M.lookup fullOrderId m, BM.lookup fullOrderId bm) of |
|
|
|
case (M.lookup fullOrderId m, BM.lookup fullOrderId bm) of |
|
|
|
(Just bro, Just globalOrderId) -> do |
|
|
|
(Just bro, Just globalOrderId) -> do |
|
|
|
cancelOrder bro globalOrderId |
|
|
|
cancelOrder bro globalOrderId |
|
|
|
return ResponseOk |
|
|
|
return $ ResponseOk requestId |
|
|
|
_ -> return $ ResponseError "Unknown order" |
|
|
|
_ -> return $ ResponseError requestId "Unknown order" |
|
|
|
RequestNotifications sqnum clientIdentity initialSqnum -> do |
|
|
|
RequestNotifications requestId clientIdentity initialSqnum -> do |
|
|
|
log Debug "Broker.Server" $ "Request: notifications:" <> (T.pack . show) request |
|
|
|
log Debug "Broker.Server" $ "Request: notifications:" <> (T.pack . show) request |
|
|
|
maybeNs <- M.lookup clientIdentity . pendingNotifications <$> readIORef state |
|
|
|
maybeNs <- M.lookup clientIdentity . pendingNotifications <$> readIORef serverState |
|
|
|
case maybeNs of |
|
|
|
case maybeNs of |
|
|
|
Just ns -> do |
|
|
|
Just ns -> do |
|
|
|
let filtered = L.filter (\n -> getNotificationSqnum n >= initialSqnum) ns |
|
|
|
let filtered = L.filter (\n -> getNotificationSqnum n >= initialSqnum) ns |
|
|
|
atomicMapIORef state (\s -> s { pendingNotifications = M.insert clientIdentity filtered (pendingNotifications s)}) |
|
|
|
atomicMapIORef serverState (\s -> s { pendingNotifications = M.insert clientIdentity filtered (pendingNotifications s)}) |
|
|
|
return $ ResponseNotifications . L.reverse $ filtered |
|
|
|
return $ ResponseNotifications requestId . L.reverse $ filtered |
|
|
|
Nothing -> return $ ResponseNotifications [] |
|
|
|
Nothing -> return $ ResponseNotifications requestId [] |
|
|
|
RequestCurrentSqnum sqnum clientIdentity -> do |
|
|
|
RequestCurrentSqnum requestId clientIdentity -> do |
|
|
|
log Debug "Broker.Server" $ "Request: current sqnum:" <> (T.pack . show) request |
|
|
|
log Debug "Broker.Server" $ "Request: current sqnum:" <> (T.pack . show) request |
|
|
|
sqnumMap <- notificationSqnum <$> readIORef state |
|
|
|
sqnumMap <- notificationSqnum <$> readIORef serverState |
|
|
|
notifMap <- pendingNotifications <$> readIORef state |
|
|
|
notifMap <- pendingNotifications <$> readIORef serverState |
|
|
|
case M.lookup clientIdentity notifMap of |
|
|
|
case M.lookup clientIdentity notifMap of |
|
|
|
Just [] -> |
|
|
|
Just [] -> |
|
|
|
case M.lookup clientIdentity sqnumMap of |
|
|
|
case M.lookup clientIdentity sqnumMap of |
|
|
|
Just sqnum -> return (ResponseCurrentSqnum sqnum) |
|
|
|
Just sqnum -> return (ResponseCurrentSqnum requestId sqnum) |
|
|
|
_ -> return (ResponseCurrentSqnum (NotificationSqnum 1)) |
|
|
|
_ -> return (ResponseCurrentSqnum requestId (NotificationSqnum 1)) |
|
|
|
Just notifs -> case lastMay notifs of |
|
|
|
Just notifs -> case lastMay notifs of |
|
|
|
Just v -> return (ResponseCurrentSqnum (getNotificationSqnum v)) |
|
|
|
Just v -> return (ResponseCurrentSqnum requestId (getNotificationSqnum v)) |
|
|
|
_ -> return (ResponseCurrentSqnum (NotificationSqnum 1)) |
|
|
|
_ -> return (ResponseCurrentSqnum requestId (NotificationSqnum 1)) |
|
|
|
Nothing -> return (ResponseCurrentSqnum (NotificationSqnum 1)) |
|
|
|
Nothing -> return (ResponseCurrentSqnum requestId (NotificationSqnum 1)) |
|
|
|
|
|
|
|
RequestSetClientIdentity requestId _ -> pure $ ResponseError requestId "Client identity change is not supported" |
|
|
|
|
|
|
|
|
|
|
|
sendMessage sock peerId resp = sendMulti sock (peerId :| [B.empty, BL.toStrict . encode $ resp]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
findBrokerForAccount account = L.find (L.elem account . accounts) |
|
|
|
findBrokerForAccount account = L.find (L.elem account . accounts) |
|
|
|
nextOrderId = atomicModifyIORef' state (\s -> ( s {orderIdCounter = 1 + orderIdCounter s}, orderIdCounter s)) |
|
|
|
nextOrderId = atomicModifyIORef' serverState (\s -> ( s {orderIdCounter = 1 + orderIdCounter s}, orderIdCounter s)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stopBrokerServer :: BrokerServerHandle -> IO () |
|
|
|
stopBrokerServer :: BrokerServerHandle -> IO () |
|
|
|
stopBrokerServer (BrokerServerHandle tid tstid compMv killMv) = do |
|
|
|
stopBrokerServer (BrokerServerHandle tid clients compMv killMv) = do |
|
|
|
putMVar killMv () |
|
|
|
putMVar killMv () |
|
|
|
killThread tstid |
|
|
|
readIORef clients >>= mapM_ (killThread . cThreadId) . M.elems |
|
|
|
yield |
|
|
|
yield |
|
|
|
readMVar compMv |
|
|
|
readMVar compMv |
|
|
|
|