natskell-1.1.0.0: internal/Plumbing/Network/Connection/Core.hs
module Network.Connection.Core
( ReadError
, WriteError
, Transport (..)
, TransportOption (..)
, Conn
, newConn
, readData
, closeReader
, openReader
, writeData
, writeDataLazy
, closeWriter
, openWriter
, openConn
, closeConn
, abortConn
, pointTransport
, currentTransport
, bufferRead
, enableReadWorker
) where
import Control.Concurrent (forkIOWithUnmask)
import Control.Concurrent.STM
import Control.Exception (SomeException, finally, onException)
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import Data.Maybe (isJust, isNothing)
import Lib.Exception (trySync)
import Network.Connection.Types
newConn :: IO Conn
newConn =
Conn
<$> newEmptyTMVarIO
<*> newEmptyTMVarIO
<*> newEmptyTMVarIO
<*> newTVarIO mempty
<*> newTBQueueIO 1000
<*> newTVarIO False
<*> newTVarIO False
readChunkSize :: Int
readChunkSize = 4096
startReadWorker :: Conn -> IO ()
startReadWorker conn = do
shouldStart <- atomically $ do
enabled <- readTVar (readWorkerEnabled conn)
running <- readTVar (readWorkerRunning conn)
if not enabled || running
then return False
else do
writeTVar (readWorkerRunning conn) True
return True
when shouldStart $
void (forkIOWithUnmask (\unmask -> unmask (readWorkerLoop conn)))
`onException` atomically (writeTVar (readWorkerRunning conn) False)
readWorkerLoop :: Conn -> IO ()
readWorkerLoop conn = do
let cleanup = atomically $ writeTVar (readWorkerRunning conn) False
finally loop cleanup
where
loop = do
(blocked, enabled) <- atomically $ do
blocked <- tryReadTMVar (readBlock conn)
enabled <- readTVar (readWorkerEnabled conn)
return (blocked, enabled)
case (blocked, enabled) of
(Just _, _) -> return ()
(_, False) -> return ()
_ -> do
current <- currentTransport conn
case current of
Nothing -> return ()
Just currentTransport' -> do
result <- trySync (transportRead currentTransport' readChunkSize)
shouldContinue <- enqueueReadResult conn result
case result of
Left _ -> return ()
Right bytes
| BS.null bytes -> return ()
| otherwise -> when shouldContinue loop
enqueueReadResult :: Conn -> Either SomeException ByteString -> IO Bool
enqueueReadResult conn result = atomically $
(do
blocked <- tryReadTMVar (readBlock conn)
check (isNothing blocked)
writeTBQueue (readQueue conn) (either (Left . show) Right result)
return True)
`orElse`
(do
_ <- readTMVar (readBlock conn)
return False)
enableReadWorker :: Conn -> IO ()
enableReadWorker conn = atomically $ writeTVar (readWorkerEnabled conn) True
readData :: Conn -> Int -> IO (Either ReadError ByteString)
readData conn n = do
blocked <- atomically $ tryReadTMVar (readBlock conn)
if isJust blocked
then return $ Left "Read operation is blocked"
else do
buffered <- atomically $ do
buf <- readTVar (readBuffer conn)
if BS.null buf
then return Nothing
else do
let (chunk, rest) = BS.splitAt n buf
writeTVar (readBuffer conn) rest
return (Just chunk)
case buffered of
Just chunk -> return $ Right chunk
Nothing -> do
current <- currentTransport conn
case current of
Nothing -> return $ Left "Transport not initialized"
Just currentTransport' -> do
enabled <- readTVarIO (readWorkerEnabled conn)
if enabled
then do
startReadWorker conn
result <- atomically $
(Left "Read operation is blocked" <$ readTMVar (readBlock conn))
`orElse`
readTBQueue (readQueue conn)
case result of
Left err -> return $ Left err
Right bytes -> do
let (chunk, rest) = BS.splitAt n bytes
unless (BS.null rest)
(atomically $ modifyTVar' (readBuffer conn) (<> rest))
return $ Right chunk
else do
resultVar <- newEmptyTMVarIO
_ <- forkIOWithUnmask $ \unmask -> unmask $ do
result <- trySync (transportRead currentTransport' n)
atomically . void $ tryPutTMVar resultVar result
result <- atomically $
(Left "Read operation is blocked" <$ readTMVar (readBlock conn))
`orElse`
(Right <$> readTMVar resultVar)
case result of
Left err -> return $ Left err
Right (Left err) -> return $ Left (show err)
Right (Right bytes) -> do
let (chunk, rest) = BS.splitAt n bytes
unless (BS.null rest)
(atomically $ modifyTVar' (readBuffer conn) (<> rest))
return $ Right chunk
closeReader :: Conn -> IO ()
closeReader conn = void . atomically $ tryPutTMVar (readBlock conn) ()
openReader :: Conn -> IO ()
openReader conn = void . atomically $ tryTakeTMVar (readBlock conn)
writeData :: Conn -> ByteString -> IO (Either WriteError ())
writeData conn bytes = do
blocked <- atomically $ tryReadTMVar (writeBlock conn)
if isJust blocked
then return $ Left "Write operation is blocked"
else do
current <- currentTransport conn
case current of
Nothing -> return $ Left "Transport not initialized"
Just currentTransport' -> do
result <- trySync $ do
transportWrite currentTransport' bytes
transportFlush currentTransport'
case result of
Left err -> return $ Left (show err)
Right _ -> return $ Right ()
writeDataLazy :: Conn -> LBS.ByteString -> IO (Either WriteError ())
writeDataLazy conn bytes = do
blocked <- atomically $ tryReadTMVar (writeBlock conn)
if isJust blocked
then return $ Left "Write operation is blocked"
else do
current <- currentTransport conn
case current of
Nothing -> return $ Left "Transport not initialized"
Just currentTransport' -> do
result <- trySync $ do
transportWriteLazy currentTransport' bytes
transportFlush currentTransport'
case result of
Left err -> return $ Left (show err)
Right _ -> return $ Right ()
closeWriter :: Conn -> IO ()
closeWriter conn = void . atomically $ tryPutTMVar (writeBlock conn) ()
openWriter :: Conn -> IO ()
openWriter conn = void . atomically $ tryTakeTMVar (writeBlock conn)
pointTransport :: Conn -> Transport -> IO ()
pointTransport conn newTransport = atomically $ do
_ <- tryTakeTMVar (transport conn)
putTMVar (transport conn) newTransport
currentTransport :: Conn -> IO (Maybe Transport)
currentTransport conn = atomically $ tryReadTMVar (transport conn)
closeConn :: Conn -> IO ()
closeConn conn = do
closeReader conn
closeWriter conn
current <- atomically (tryTakeTMVar (transport conn))
case current of
Nothing -> return ()
Just currentTransport' -> void $ trySync (transportClose currentTransport')
waitForReadWorkerStopped conn
abortConn :: Conn -> IO ()
abortConn conn = do
current <- atomically (tryTakeTMVar (transport conn))
case current of
Nothing -> pure ()
Just currentTransport' -> void $ trySync (transportAbort currentTransport')
openConn :: Conn -> IO ()
openConn conn = do
openReader conn
openWriter conn
atomically $ do
writeTVar (readBuffer conn) mempty
void $ flushTBQueue (readQueue conn)
writeTVar (readWorkerEnabled conn) False
bufferRead :: Conn -> ByteString -> IO ()
bufferRead conn bytes =
unless (BS.null bytes)
(atomically $ modifyTVar' (readBuffer conn) (bytes <>))
waitForReadWorkerStopped :: Conn -> IO ()
waitForReadWorkerStopped conn =
atomically $ do
running <- readTVar (readWorkerRunning conn)
when running retry