Can a Haskell or Haskell OS thread waiting on Network.Socket.accept not be killed on Windows?

asynchronous, exception, haskell, multithreading, networking

Solution

Interesting question!

You can't interrupt blocking foreign calls, so I'm somewhat surprised that you're able to interrupt the thread on Linux. Also, `forkOS` doesn't help -- that just lets foreign code allocate thread-local storage, but has nothing to do with blocking behavior. But recall that accept can be set to non-blocking:

If no pending connections are present on the queue, and the socket is not marked as nonblocking, accept() blocks the caller until a connection is present. If the socket is marked nonblocking and no pending connections are present on the queue, accept() fails with the error EAGAIN or EWOULDBLOCK.

Which is what is done in the Network library for Posix systems. This then allows the `accept` to be interrupted.

An interesting note about Windows:

-- On Windows, our sockets are not put in non-blocking mode (non-blocking
-- is not supported for regular file descriptors on Windows, and it would
-- be a pain to support it only for sockets).  So there are two cases:
--
--  - the threaded RTS uses safe calls for socket operations to get
--    non-blocking I/O, just like the rest of the I/O library
--
--  - with the non-threaded RTS, only some operations on sockets will be
--    non-blocking.  Reads and writes go through the normal async I/O
--    system.  accept() uses asyncDoProc so is non-blocking.  A handful
--    of others (recvFrom, sendFd, recvFd) will block all threads - if this
--    is a problem, -threaded is the workaround.

Now, accept on Windows, with the -threaded runtime, uses accept_safe (which allows other threads to make progress) -- but it doesn't put the socket into non-blocking mode:

accept sock@(MkSocket s family stype protocol status) = do
 currentStatus <- readMVar status
 okay <- sIsAcceptable sock
 if not okay
   then
     ioError (userError ("accept: can't perform accept on socket (" ++ (show (family,stype,protocol)) ++") in status " ++
     show currentStatus))
   else do
     let sz = sizeOfSockAddrByFamily family
     allocaBytes sz $ \ sockaddr -> do

#if defined(mingw32_HOST_OS) && defined(__GLASGOW_HASKELL__)
     new_sock <-
    if threaded
       then with (fromIntegral sz) $ \ ptr_len ->
          throwErrnoIfMinus1Retry "Network.Socket.accept" $
            c_accept_safe s sockaddr ptr_len
       else do
            paramData <- c_newAcceptParams s (fromIntegral sz) sockaddr
            rc        <- asyncDoProc c_acceptDoProc paramData
            new_sock  <- c_acceptNewSock    paramData
            c_free paramData
            when (rc /= 0)
                 (ioError (errnoToIOError "Network.Socket.accept" (Errno (fromIntegral rc)) Nothing Nothing))
        return new_sock

Since 2005, versions of the `network` package, on Windows with -threaded explicitly use an accept call marked as `safe`, allowing other threads to make progress, but not setting the socket itself into non-blocking mode (so the calling thread blocks).

To work around it I see two options:

- work out how to make a non-blocking accept call on Windows, and patch the network library -- look at what e.g. snap or yesod do here, to see if they already solved it.

- use some kind of supervisory thread to fake epoll, monitoring the blocked child threads for progress.

Problem

``` -- thread A t <- forkIO $ do _ <- accept listener -- blocks ``` ``` -- thread B killThread t ``` works on Linux (probably also on OS X and FreeBSD) but not on Windows (tried -threaded with +RTS -N4 -RTS etc.). - What is the correct way to terminate thread `A` in this case? - Is there a way to fork thread `A` in a special mode that would allow termination at the point it blocks on `accept`? - Would it help if `A` were forked with `forkOS` rather than `forkIO`? I noticed this deviant Windows behaviour only when alerted to by a bug report.

Original source

Related problems