haskell-socket icon indicating copy to clipboard operation
haskell-socket copied to clipboard

Possible (but unlikely) double close

Open treeowl opened this issue 4 years ago • 1 comments

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.

treeowl avatar Dec 27 '20 21:12 treeowl

The sure fix: after returning from bracketOnError, call touch# on the MVar#.

treeowl avatar Dec 27 '20 22:12 treeowl