packages feed

recv-0.1.2: Network/Socket/BufferPool/Buffer.hs

module Network.Socket.BufferPool.Buffer (
    newBufferPool,
    withBufferPool,
    tryWithBufferPool,
    mallocBS,
    copy,
) where

import qualified Data.ByteString as BS
import Data.ByteString.Internal (ByteString (..))
import Data.IORef (newIORef, readIORef, writeIORef)
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc (finalizerFree, mallocBytes)
import Foreign.Marshal.Utils (copyBytes)
import Foreign.Ptr (castPtr, plusPtr)

import Network.Socket.BufferPool.Types

----------------------------------------------------------------

-- | Creating a buffer pool.
--   The first argument is the lower limit.
--   When the size of the buffer in the poll is lower than this limit,
--   the buffer is thrown awany (and is eventually freed).
--   Then a new buffer is allocated.
--   The second argument is the size for the new allocation.
newBufferPool :: Int -> Int -> IO BufferPool
newBufferPool l h = BufferPool l h <$> newIORef BS.empty

----------------------------------------------------------------

-- | Using a buffer pool.
--   The second argument is a function which returns
--   how many bytes are filled in the buffer.
--   This function should return non negative 'Int'.
--   The buffer in the buffer pool is automatically managed.
withBufferPool :: BufferPool -> (Buffer -> BufSize -> IO Int) -> IO ByteString
withBufferPool pool@(BufferPool _ _ ref) f = do
    (buf, consumed) <- applyBufferPool pool f
    writeIORef ref $ BS.drop consumed buf
    return $ BS.take consumed buf

-- | L ike 'withBufferPool' for fillers that can decline to fill:
--   a negative return value from the filler leaves the pool untouched
--   and produces 'Nothing'.
tryWithBufferPool
    :: BufferPool -> (Buffer -> BufSize -> IO Int) -> IO (Maybe ByteString)
tryWithBufferPool pool@(BufferPool _ _ ref) f = do
    (buf, consumed) <- applyBufferPool pool f
    if consumed < 0
        then do
            writeIORef ref buf
            return Nothing
        else do
            writeIORef ref $ BS.drop consumed buf
            return $ Just $ BS.take consumed buf

applyBufferPool
    :: BufferPool -> (Buffer -> BufSize -> IO Int) -> IO (ByteString, Int)
applyBufferPool (BufferPool l h ref) f = do
    buf0 <- readIORef ref
    buf <-
        if BS.length buf0 >= l
            then return buf0
            else mallocBS h
    consumed <- withForeignBuffer buf f
    return (buf, consumed)

withForeignBuffer :: ByteString -> (Buffer -> BufSize -> IO Int) -> IO Int
withForeignBuffer (PS ps s l) f = withForeignPtr ps $ \p -> f (castPtr p `plusPtr` s) l
{-# INLINE withForeignBuffer #-}

----------------------------------------------------------------

-- | Allocating a byte string.
mallocBS :: Int -> IO ByteString
mallocBS size = do
    ptr <- mallocBytes size
    fptr <- newForeignPtr finalizerFree ptr
    return $ PS fptr 0 size
{-# INLINE mallocBS #-}

-- | Copying the bytestring to the buffer.
--   This function returns the point where the next copy should start.
copy :: Buffer -> ByteString -> IO Buffer
copy ptr (PS fp o l) = withForeignPtr fp $ \p -> do
    copyBytes ptr (p `plusPtr` o) (fromIntegral l)
    return $ ptr `plusPtr` l
{-# INLINE copy #-}