haskell-socket
haskell-socket copied to clipboard
Possible (but unlikely) double close
If a socket is opened, but then never used, the garbage collector runs at exactly the wrong time, and an asynchronous exception is delivered at exactly the wrong time, then socket
could hypothetically try to close the socket twice.
socket :: (Family f, Type t, Protocol p) => IO (Socket f t p)
socket = socket'
where
socket' :: forall f t p. (Family f, Type t, Protocol p) => IO (Socket f t p)
socket' = alloca $ \errPtr-> do
bracketOnError
-- Try to acquire the socket resource. This part has exceptions masked.
( c_socket (familyNumber (undefined :: f)) (typeNumber (undefined :: t)) (protocolNumber (undefined :: p)) errPtr )
-- On failure after the c_socket call we try to close the socket to not leak file descriptors.
-- If closing fails we cannot really do something about it. We tried at least.
-- c_close is an unsafe FFI call.
( \fd-> when (fd >= 0) $ alloca $ void . c_close fd )
( \fd-> do
when (fd < 0) (SocketException <$> peek errPtr >>= throwIO)
mfd <- newMVar fd
let s = Socket mfd
_ <- mkWeakMVar mfd (close s)
-- The danger zone
return s
)
If the garbage collector runs in what I've labeled "the danger zone" (between finalizer installation and exiting bracketOnError
), and mfd
is found to be dead, then the finalizer will run, closing the socket. If an exception is then delivered immediately, then the exception handler will attempt to close the socket again. Can this really happen? That's not entirely clear to me, but I don't believe there's any documented guarantee that it can't.
The sure fix: after returning from bracketOnError
, call touch#
on the MVar#
.