{- | Pipelining is sending multiple requests over a socket and receiving the responses later, in the same order. This is faster than sending one request, waiting for the response, then sending the next request, and so on. This implementation returns a /promise (future)/ response for each request that when invoked waits for the response if not already arrived. Multiple threads can send on the same pipe (and get promises back); the pipe will pipeline each thread's request right away without waiting. -}
{-# LANGUAGE DoRec, RecordWildCards, MultiParamTypeClasses, FlexibleContexts #-}
vHandle::MVarhandle,-- ^ Mutex on handle, so only one thread at a time can write to it
responseQueue::Chan(MVar(EitherIOErrorbytes)),-- ^ Queue of threads waiting for responses. Every time a response arrive we pop the next thread and give it the response.
listenThread::ThreadId
}
-- | Create new Pipe with given encodeInt, decodeInt, and handle. You should 'close' pipe when finished, which will also close handle. If pipe is not closed but eventually garbage collected, it will be closed along with handle.
newPipe::(Streamhb,ResourceIOh)=>
(Size->b)-- ^ Convert Size to bytes of fixed length. Every Int must translate to same number of bytes.
->(b->Size)-- ^ Convert bytes of fixed length to Size. Must be exact inverse of encodeSize.
->h-- ^ Underlying socket (handle) this pipe will read/write from
->IO(Pipehb)
newPipeencodeSizedecodeSizehandle=do
vHandle<-newMVarhandle
responseQueue<-newChan
rec
letpipe=Pipe{..}
listenThread<-forkIO(listenpipe)
addMVarFinalizervHandle$do
killThreadlistenThread
closehandle
returnpipe
instance(ResourceIOh)=>ResourceIO(Pipehb)where
-- | Close pipe and underlying socket (handle)
closePipe{..}=do
killThreadlistenThread
close=<<readMVarvHandle
isClosedPipe{..}=isClosed=<<readMVarvHandle
listen::(Streamhb)=>Pipehb->IO()
-- ^ Listen for responses and supply them to waiting threads in order
listenPipe{..}=do
letn=length(encodeSize0)
h<-readMVarvHandle
forever$do
e<-try$do
len<-decodeSize<$>getNhn
getNhlen
var<-readChanresponseQueue
putMVarvare
send::(Streamhb)=>Pipehb->[b]->IO()
-- ^ Send messages all together to destination (no messages will be interleaved between them). None of the messages can induce a response, i.e. the destination must not reply to any of these messages (otherwise future 'call's will get these responses instead of their own).
-- Each message is preceeded by its length when written to socket.
sendPipe{..}messages=withMVarvHandle$\h->do
mapM_(writeencodeSizeh)messages
flushh
call::(Streamhb)=>Pipehb->[b]->IO(IOb)
-- ^ Send messages all together to destination (no messages will be interleaved between them), and return /promise/ of response from one message only. One and only one message in the list must induce a response, i.e. the destination must reply to exactly one message only (otherwise promises will have the wrong responses in them).
-- Each message is preceeded by its length when written to socket. Likewise, the response must be preceeded by its length.