packages feed

hpgsql-0.1.0.0: src/Hpgsql/Networking.hs

-- |
-- This module contains code largely copied from the @network@ library
-- (BSD-3-Clause), with modifications to remove blocking calls
-- (threadWaitWrite, threadWaitRead).
--
-- Original copyright:
--   Copyright (c) 2002-2010, The University Court of the University of Glasgow
--   Copyright (c) 2007-2010, Johan Tibell
--
-- See: https://github.com/haskell/network/blob/master/LICENSE
module Hpgsql.Networking
  ( recvNonBlocking,
    socketWaitRead,
    socketWaitWrite,
    sendNonBlocking,
  )
where

import Control.Concurrent (threadWaitRead, threadWaitWrite)
import Control.Exception.Safe (throw)
import Data.ByteString (ByteString)
import Data.ByteString.Internal (createAndTrim)
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Int (Int64)
import Foreign (Ptr, Storable (..), Word8, allocaArray, castPtr, nullPtr, plusPtr)
import Foreign.C (CChar (..), CInt (..), CSize (..), eAGAIN, eWOULDBLOCK, getErrno)
import Network.Socket (Socket, withFdSocket)
import System.Posix.Types (CSsize (..))

socketWaitRead :: Socket -> IO ()
socketWaitRead socket = withFdSocket socket (threadWaitRead . fromIntegral)

socketWaitWrite :: Socket -> IO ()
socketWaitWrite socket = withFdSocket socket (threadWaitWrite . fromIntegral)

recvNonBlocking :: Socket -> CSize -> IO ByteString
recvNonBlocking s nbytes = withFdSocket s $ \fd -> createAndTrim (fromIntegral nbytes) $ \buffer -> do
  -- Largely copied from https://hackage-content.haskell.org/package/network-3.2.8.0/docs/src/Network.Socket.Buffer.html#recvBufNoWait and other functions from the network library,
  -- but then modified to our needs.
  r <- c_recv fd (castPtr buffer) nbytes 0 {-flags-}
  if r >= 0
    then do
      -- putStrLn $ "Asked for " ++ show nbytes ++ ", got " ++ show r
      pure $ fromIntegral r
    else do
      err <- getErrno
      if err == eAGAIN || err == eWOULDBLOCK
        then do
          -- putStrLn $ "Asked for " ++ show nbytes ++ ", but got eAGAIN"
          pure 0
        else
          throw $ userError "Internal error in hpgsql's recvNonBlocking"

sendNonBlocking :: Socket -> L.ByteString -> IO Int64
sendNonBlocking s lbs = do
  -- Largely copied from https://hackage-content.haskell.org/package/network-3.2.8.0/docs/src/Network.Socket.ByteString.Lazy.Posix.html#send,
  -- but then modified to our needs.
  let cs = take maxNumChunks (L.toChunks lbs)
      len = length cs
  siz <- withFdSocket s $ \fd -> allocaArray len $ \ptr ->
    withPokes cs ptr $ \niovs ->
      -- This part has `throwSocketErrorWaitWrite s "writev"` in the
      -- original codebase, but we don't have that here because that
      -- calls threadWaitWrite, which blocks.
      c_writev fd ptr niovs
  return $ fromIntegral siz
  where
    withPokes ss p f = loop ss p 0 0
      where
        loop (c : cs) q k niovs
          | k < maxNumBytes = unsafeUseAsCStringLen c $ \(ptr, len) -> do
              poke q $ IOVec (castPtr ptr) (fromIntegral len)
              loop
                cs
                (q `plusPtr` sizeOf (IOVec nullPtr 0))
                (k + fromIntegral len)
                (niovs + 1)
          | otherwise = f niovs
        loop _ _ _ niovs = f niovs
    maxNumBytes = 4194304 :: Int -- maximum number of bytes to transmit in one system call
    maxNumChunks = 1024 :: Int -- maximum number of chunks to transmit in one system call

data IOVec = IOVec
  { iovBase :: Ptr Word8,
    iovLen :: CSize
  }

instance Storable IOVec where
  sizeOf ~_ = (16)
  alignment ~_ = alignment (0 :: CInt)

  peek p = do
    base <- ((\hsc_ptr -> peekByteOff hsc_ptr 0)) p
    len <- ((\hsc_ptr -> peekByteOff hsc_ptr 8)) p
    return $ IOVec base len

  poke p iov = do
    ((\hsc_ptr -> pokeByteOff hsc_ptr 0)) p (iovBase iov)
    ((\hsc_ptr -> pokeByteOff hsc_ptr 8)) p (iovLen iov)

-- -- | @withIOVec cs f@ executes the computation @f@, passing as argument a pair
-- -- consisting of a pointer to a temporarily allocated array of pointers to
-- -- IOVec made from @cs@ and the number of pointers (@length cs@).
-- -- /Unix only/.
-- withIOVec :: [(Ptr Word8, Int)] -> ((Ptr IOVec, Int) -> IO a) -> IO a
-- withIOVec [] f = f (nullPtr, 0)
-- withIOVec cs f =
--   allocaArray csLen $ \aPtr -> do
--     zipWithM_ pokeIov (ptrs aPtr) cs
--     f (aPtr, csLen)
--   where
--     csLen = length cs
--     ptrs = iterate (`plusPtr` sizeOf (IOVec nullPtr 0))
--     pokeIov ptr (sPtr, sLen) = poke ptr $ IOVec sPtr (fromIntegral sLen)

foreign import ccall unsafe "recv"
  c_recv :: CInt -> Ptr CChar -> CSize -> CInt -> IO CInt

foreign import ccall unsafe "writev"
  c_writev :: CInt -> Ptr IOVec -> CInt -> IO CSsize