spacecookie-1.0.0.0: src/Network/Gopher/Util/Socket.hs
-- | Internal socket utilities implementing missing
-- features of 'System.Socket' which are yet to be
-- upstreamed.
module Network.Gopher.Util.Socket
( gracefulClose
) where
import Control.Concurrent.MVar (withMVar)
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (race)
import Control.Exception.Base (throwIO)
import Control.Monad (void, when)
import Data.Functor ((<&>))
import Foreign.C.Error (Errno (..), getErrno)
import Foreign.C.Types (CInt (..))
import System.Socket (receive, msgNoSignal, SocketException (..), close, Family ())
import System.Socket.Type.Stream (Stream ())
import System.Socket.Protocol.TCP (TCP ())
import System.Socket.Unsafe (Socket (..))
-- Until https://github.com/lpeterse/haskell-socket/pull/67 gets
-- merged, we have to implement shutdown ourselves.
foreign import ccall unsafe "shutdown"
c_shutdown :: CInt -> CInt -> IO CInt
data ShutdownHow
-- | Disallow Reading (calls to 'receive' are empty).
= ShutdownRead
-- | Disallow Writing (calls to 'send' throw).
| ShutdownWrite
-- | Disallow both.
| ShutdownReadWrite
deriving (Show, Eq, Ord, Enum)
-- | Shutdown a stream connection (partially).
-- Will send TCP FIN and prompt a client to
-- close the connection.
--
-- Not exposed to prevent future name clash.
shutdown :: Socket a Stream TCP -> ShutdownHow -> IO ()
shutdown (Socket mvar) how = withMVar mvar $ \fd -> do
res <- c_shutdown (fromIntegral fd)
$ fromIntegral $ fromEnum how
when (res /= 0) $ throwIO =<<
(getErrno <&> \(Errno errno) -> SocketException errno)
-- | Shutdown connection and give client a bit
-- of time to clean up on its end before closing
-- the connection to avoid a broken pipe on the
-- other side.
gracefulClose :: Family f => Socket f Stream TCP -> IO ()
gracefulClose sock = do
-- send TCP FIN
shutdown sock ShutdownWrite
-- wait for some kind of read from the
-- client (either mempty, meaning TCP FIN,
-- something else which would mean protocol
-- violation). Give up after 1s.
_ <- race (void $ receive sock 16 msgNoSignal) (threadDelay 1000000)
close sock