Opened 15 months ago

Last modified 15 months ago

#14503 new bug

Killing a thread will block if there is another process reading from a handle

Reported by: dnadales Owned by:
Priority: normal Milestone: 8.2.1
Component: libraries/base Version: 8.0.2
Keywords: Cc:
Operating System: Windows Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

When trying to kill a thread, the program (which uses a thread) hangs if there is another process trying to read from a handle. This bug can be reproduced with using this sample code. I'll explain the relevant details below.

I have the following Haskell code:

someFuncWithChans :: IO ()
someFuncWithChans = withSocketsDo $ do
    h <- connectTo "localhost" (PortNumber 9090)
    hSetBuffering h NoBuffering
    ch <- newChan
    putStrLn "Starting the handler reader"
    readerTid <- forkIO $ handleReader h ch
    cmdsHandler h ch
    putStrLn "Killing the handler reader"
    killThread readerTid
    putStrLn "Closing the handle"
    hClose h

cmdsHandler :: Handle -> Chan Action -> IO ()
cmdsHandler h ch = do
    act <- readChan ch
    case act of
      Quit      -> putStrLn "Bye bye"
      Line line -> do
            hPutStrLn h (reverse line)
            cmdsHandler h ch

handleReader :: Handle -> Chan Action -> IO ()
handleReader h ch = forever $ do
    line <- strip <$> hGetLine h
    case line of
        "quit" -> writeChan ch Quit
        _      -> writeChan ch (Line line)

data Action = Quit | Line String

Is the function someFuncWithChans is run along with the following Java program, then the former will block while killing the handler reader (readerTid).

    public static void main(String[] args) throws IOException, InterruptedException {

        ServerSocket serverSock = new ServerSocket(9090);

        Socket sock = serverSock.accept();

        InputStream inStream = sock.getInputStream();
        BufferedReader sockIn = new BufferedReader(new InputStreamReader(inStream));

        OutputStream outStream = sock.getOutputStream();
        PrintWriter sockOut = new PrintWriter(new OutputStreamWriter(outStream));



        while (true) {
            Thread.sleep(1000);
            System.out.println("Sending foo");
            sockOut.println("foo");
            sockOut.flush();
            String s = sockIn.readLine();
            System.out.println("Got " + s );
            Thread.sleep(1000);
            System.out.println("Sending bar");
            sockOut.println("bar");
            sockOut.flush();
            s = sockIn.readLine();
            System.out.println("Got " + s );
            Thread.sleep(1000);
            System.out.println("Sending quit");
            sockOut.println("quit");
            sockOut.flush();
            // This will cause someFuncWithChans to block when killing the
            // reader thread.
            s = sockIn.readLine();
            System.out.println("Got " + s );
        }
    }

If the sockIn.readLine() is commented out, then killing the thread will succeed. This problem appears only on my Windows machine (at work), whereas it does not on my personal Linux machine.

Change History (11)

comment:1 Changed 15 months ago by dnadales

Component: Compilerlibraries/base

The problem can be easily reproduced by running these two programs in parallel:

readWithinNSecs :: IO ()
readWithinNSecs = withSocketsDo $ do
  h <- connectTo "localhost" (PortNumber 9090)
  hSetBuffering h NoBuffering
  readerTid <- forkIO $ reader h
  threadDelay $ 2 * 10^6
  putStrLn "Killing the reader"
  killThread readerTid
  putStrLn "Reader thread killed"
  where
    reader h = do
      line <- strip <$> hGetLine h
      putStrLn $ "Got " ++ line
import java.net.*;
import java.io.*;

public class Reader {
    public static void main(String[] args) throws IOException, InterruptedException {

        ServerSocket serverSock = new ServerSocket(9090);

        Socket sock = serverSock.accept();

        InputStream inStream = sock.getInputStream();
        BufferedReader sockIn = new BufferedReader(new InputStreamReader(inStream));

        while (true) {
            Thread.sleep(1000);
            System.out.println("Trying to read something...");
            String s = sockIn.readLine();
            System.out.println("Got " + s );
        }
    }
}

comment:2 Changed 15 months ago by bgamari

Thanks for the repro!

comment:3 Changed 15 months ago by bgamari

Here is an complete all-Haskell repro,

-- Server.hs
import System.IO
import Network
import Control.Monad
import Control.Concurrent

main = withSocketsDo $ do
    sock <- listenOn $ PortNumber 9090
    (h, _, _) <- accept sock
    forever $ do
        threadDelay 100000
        putStrLn "Trying to read something"
        s <- hGetLine h
        putStrLn $ "Got "++s
-- Client.hs
import Network
import System.IO
import Control.Concurrent

main = readWithinNSecs

readWithinNSecs :: IO ()
readWithinNSecs = withSocketsDo $ do
  h <- connectTo "localhost" (PortNumber 9090)
  hSetBuffering h NoBuffering
  readerTid <- forkIO $ reader h
  threadDelay $ 2 * 10^6
  putStrLn "Killing the reader"
  killThread readerTid
  putStrLn "Reader thread killed"
  where
    reader h = do
      line <- hGetLine h
      putStrLn $ "Got " ++ line

Expected output (as seen on Debian 9 with GHC 8.2.2),

$ ghc Server.hs
$ ghc Client.hs
$ ./Server & ./Client
Trying to read something
Killing the reader
Reader thread killed
Server: <socket: 4>: hGetLine: end of file

Observed output (on Windows 10 with 64-bit GHC 8.2.1),

$ ghc Server.hs
$ ghc -threaded Client.hs
$ ./Server & ./Client
Killing the reader
Reader thread killed
Trying to read something
server.exe: <socket: 372>: hGetLine: failed (Unknown error)

Seems like things are working as expected.

comment:4 Changed 15 months ago by bgamari

Milestone: 8.2.1

Ahh, but indeed it hangs when Client is built with 8.0.2. It looks like this is fixed.

comment:5 Changed 15 months ago by bgamari

Resolution: fixed
Status: newclosed

Unfortunately it's not entirely clear how to add a testcase for this as it seems to require a socket.

comment:6 Changed 15 months ago by dnadales

Thanks for checking this out. I've tried your example with GHC 8.2.1, and it also hangs when killing the reader. I'm also running a 64-bit version of Windows 10, so I don't know what might be happening.

How did you launch the two processes? On a Unix like terminal emulator? (I've tried on MINGW64, but there it is even worse since I don't see any output). In my case I was using Windows Power Shell, but I get the same result in the old Windows Command Prompt.

comment:7 Changed 15 months ago by dnadales

I'm using stack as build tool. To discard the possibility of any noise introduced by it, I downloaded the 8.2.1 version of the Haskell platform and I still get the same results.

comment:8 Changed 15 months ago by dnadales

And regarding the tests, isn't it possible to include the example you posted above as an integration test for the Control.Concurrent library?

comment:9 Changed 15 months ago by dnadales

Reading from sockets leads to the same behavior. However, closing the socket before killing the thread works:

import qualified Data.ByteString.Char8     as C
import           Network.Socket            hiding (recv, recvFrom, send, sendTo)
import           Network.Socket.ByteString

readWithinNSecsBinary :: IO ()
readWithinNSecsBinary = withSocketsDo $ do
  addrinfos <- getAddrInfo Nothing (Just "") (Just "9090")
  let serveraddr = head addrinfos
  sock <- socket (addrFamily serveraddr) Stream defaultProtocol
  connect sock (addrAddress serveraddr)
  readerTid <- forkIO $ sockReader sock
  threadDelay (3 * 10^6)
  putStrLn "Killing the binary reader"
  putStrLn "Closing the socket"
  close sock -- Wihout this line the client will block
  putStrLn "Socket closed!"
  killThread readerTid
  putStrLn "Binary reader thread killed"
  where
    sockReader sock = do
      putStrLn "Receiving ..."
      msg <- recv sock 1024
      putStr "Received "
      C.putStrLn msg

This results in

Receiving ...
Killing the binary reader
Closing the socket
Socket closed!
dangling-connections-exe.EXE: Network.Socket.recvBuf: failed (No error)
Binary reader thread killed

comment:10 Changed 15 months ago by bgamari

Resolution: fixed
Status: closednew

Reopening as the reporter still sees this in 8.2.1.

How did you launch the two processes? On a Unix like terminal emulator? (I've tried on MINGW64, but there it is even worse since I don't see any output). In my case I was using Windows Power Shell, but I get the same result in the old Windows Command Prompt.

Yes, in a mingw64 msys2 environment under bash.

And regarding the tests, isn't it possible to include the example you posted above as an integration test for the Control.Concurrent library?

We could propose that it be added to async, but that won't help catch if GHC regresses.

comment:11 Changed 15 months ago by dnadales

Sorry that I don't quote your message but I'm replying from the phone.

Could you see if the bug is reproducible from a windows command prompt?

And for adding tests I guess it should not be added to async since it is not related to it (I would say). I don't know if I can help somehow. I'll be happy to write a small test, but if we cannot use sockets in the test then there's nothing I can do (besides setting up appveyor in a personal repository and check for this kind of regressions periodically).

Note: See TracTickets for help on using tickets.