Skip to content

Possible (but unlikely) double close #65

@treeowl

Description

@treeowl

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions