-
Notifications
You must be signed in to change notification settings - Fork 202
Description
This library's implementation of accept can leak file descriptors if the thread calling accept receives an asynchronous (throwTo) exception during the execution of accept. The current version, with CPP removed to show only the codepath for Linux, looks like this:
accept :: SocketAddress sa => Socket -> IO (Socket, sa)
accept listing_sock = withNewSocketAddress $ \new_sa sz ->
withFdSocket listing_sock $ \listing_fd -> do
new_sock <- callAccept listing_fd new_sa sz >>= mkSocket
new_addr <- peekSocketAddress new_sa
return (new_sock, new_addr)
where
callAccept fd sa sz = with (fromIntegral sz) $ \ ptr_len -> do
throwSocketErrorWaitRead listing_sock "Network.Socket.accept"
(c_accept4 fd sa ptr_len (sockNonBlock .|. sockCloexec))
After callAccept but before mkSocket, an asynchronous exception could be received. One simple solution is to mask exceptions:
new_sock <- mask_ (callAccept listing_fd new_sa sz >>= mkSocket)
Now, even if an exception is delivered, the finalizer associated with the socket should close it. Note: I am assuming that mkSocket is not interruptible, and if this assumption is wrong, none of this will work. It is defined as
mkSocket :: CInt -> IO Socket
mkSocket fd = do
ref <- newIORef fd
let s = Socket ref fd
void $ mkWeakIORef ref $ close s
return s
And the documentation in Control.Exception makes it clear that newIORef is not interruptible. I believe that mkWeakIORef is also not interruptible.