superbuffer-0.1.0.0: src/Data/ByteString/SuperBuffer.hs
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.ByteString.SuperBuffer
( SuperBuffer, withBuffer, appendBuffer
)
where
import Foreign
import Foreign.C
import Control.Exception
import Data.Coerce
import qualified Data.ByteString as BS
import qualified Data.ByteString.Unsafe as BS
-- | The buffer. Internally only a pointer to a C struct. Don't worry,
-- this module attempts to make usage of the SuperBuffer as safe as possible in
-- terms of memory leaks (with exceptions).
newtype SuperBuffer
= SuperBuffer SuperBufferP
-- | Allocate a new buffer with a given initial size. The perfect starting point
-- depends on the expected total size and the average size for a single chunk
-- written with 'appendBuffer'. You can always start with 1024 and optimize from
-- there with benchmarks. Please note that the SuperBuffer will no longer be
-- valid after this function terminates, so do NOT pass it to some other
-- thread without waiting for it to finish in the action.
withBuffer :: Int64 -> (SuperBuffer -> IO ()) -> IO BS.ByteString
withBuffer size action =
bracket (newBuffer size) destroyBuffer $ \buf ->
do ok <- try (action buf)
case ok of
Left (exception :: SomeException) ->
do destroyBufferContents buf
throwIO exception
Right () ->
readBuffer buf -- if something goes to shit here, we could be in trouble...
{-# INLINE withBuffer #-}
newBuffer :: Int64 -> IO SuperBuffer
newBuffer size = SuperBuffer <$> new_sbuf (fromIntegral size)
{-# INLINE newBuffer #-}
-- | Write a bytestring to the buffer and grow the buffer if needed. Note that only
-- one thread at any given time may call this function, so if you are sharing the
-- 'SuperBuffer' between threads make sure you place some type of guarding/locking around
-- this function.
appendBuffer :: SuperBuffer -> BS.ByteString -> IO ()
appendBuffer (SuperBuffer ptr) bs =
BS.unsafeUseAsCStringLen bs $ \(cstr, len) ->
append_sbuf ptr cstr (fromIntegral len)
{-# INLINE appendBuffer #-}
destroyBuffer :: SuperBuffer -> IO ()
destroyBuffer (SuperBuffer ptr) = destroy_sbuf ptr
{-# INLINE destroyBuffer #-}
destroyBufferContents :: SuperBuffer -> IO ()
destroyBufferContents (SuperBuffer ptr) = destroyContents_sbuf ptr
{-# INLINE destroyBufferContents #-}
-- | Read the final buffer contents. This must only
-- be called once
readBuffer :: SuperBuffer -> IO BS.ByteString
readBuffer (SuperBuffer ptr) =
do (cstr, size) <- readLocal
BS.unsafePackCStringFinalizer (coerce cstr) (fromIntegral size) (free cstr)
where
readLocal =
alloca $ \sizePtr ->
do cstr <- read_sbuf ptr sizePtr
size <- peek sizePtr
pure (cstr, size)
{-# INLINE readBuffer #-}
data SBuf
type SuperBufferP = Ptr SBuf
foreign import ccall unsafe "new_sbuf" new_sbuf :: CSize -> IO SuperBufferP
foreign import ccall unsafe "append_sbuf" append_sbuf :: SuperBufferP -> CString -> CSize -> IO ()
foreign import ccall unsafe "read_sbuf" read_sbuf :: SuperBufferP -> Ptr CSize -> IO CString
foreign import ccall unsafe "destroy_sbuf" destroy_sbuf :: SuperBufferP -> IO ()
foreign import ccall unsafe "destroyContents_sbuf" destroyContents_sbuf :: SuperBufferP -> IO ()