Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

-- 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.

like image 533
Cetin Sert Avatar asked May 15 '12 20:05

Cetin Sert


1 Answers

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.
like image 199
Don Stewart Avatar answered Nov 15 '22 13:11

Don Stewart