Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8081395

Impossible to disconnect HttpURLConnection if the server stops sending data

    Details

    • Subcomponent:
    • CPU:
      x86_64
    • OS:
      windows_7

      Description

      FULL PRODUCT VERSION :
      java version "1.8.0_45"
      Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
      Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Windows 7 64-bit

      A DESCRIPTION OF THE PROBLEM :
      I have two threads: an UI thread and a download thread. The download thread downloads data using HttpURLConnection, the UI shows a progress bar and a Cancel button. The cancel buttons calls HttpURLConnection.disconnect(). This call blocks until some more data arrive from the server, which, in my case, is tens of minutes, which is unacceptable to the user, as the UI thread is blocked.

      The connection thread is blocked at:
      "pool-1-thread-1" #101 prio=5 os_prio=0 tid=0x17663000 nid=0x2530 runnable [0x1affe000]
         java.lang.Thread.State: RUNNABLE
      at java.net.SocketInputStream.socketRead0(Native Method)
      at java.net.SocketInputStream.socketRead(Unknown Source)
      at java.net.SocketInputStream.read(Unknown Source)
      at java.net.SocketInputStream.read(Unknown Source)
      at sun.security.ssl.InputRecord.readFully(Unknown Source)
      at sun.security.ssl.InputRecord.readV3Record(Unknown Source)
      at sun.security.ssl.InputRecord.read(Unknown Source)
      at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
      - locked <0x06540f50> (a java.lang.Object)
      at sun.security.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
      at sun.security.ssl.AppInputStream.read(Unknown Source)
      - locked <0x0656b368> (a sun.security.ssl.AppInputStream)
      at java.io.BufferedInputStream.fill(Unknown Source)
      at java.io.BufferedInputStream.read1(Unknown Source)
      at java.io.BufferedInputStream.read(Unknown Source)
      - locked <0x066f4070> (a java.io.BufferedInputStream)
      at sun.net.www.http.ChunkedInputStream.readAheadBlocking(Unknown Source)
      at sun.net.www.http.ChunkedInputStream.readAhead(Unknown Source)
      at sun.net.www.http.ChunkedInputStream.read(Unknown Source)
      - locked <0x066f8da0> (a sun.net.www.http.ChunkedInputStream)
      at java.io.FilterInputStream.read(Unknown Source)
      at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(Unknown Source)
      at org.openstreetmap.josm.io.ProgressInputStream.read(ProgressInputStream.java:59)
      ... (more)

      The UI thread is blocked at:
      "AWT-EventQueue-0" #15 prio=6 os_prio=0 tid=0x167bc000 nid=0xa04 waiting for monitor entry [0x1610d000]
         java.lang.Thread.State: BLOCKED (on object monitor)
      at sun.net.www.http.ChunkedInputStream.close(Unknown Source)
      - waiting to lock <0x066f8da0> (a sun.net.www.http.ChunkedInputStream)
      at java.io.FilterInputStream.close(Unknown Source)
      at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.close(Unknown Source)
      at sun.net.www.protocol.http.HttpURLConnection.disconnect(Unknown Source)
      at sun.net.www.protocol.https.HttpsURLConnectionImpl.disconnect(Unknown Source)
      at org.openstreetmap.josm.io.OsmConnection.cancel(OsmConnection.java:66)
      ... (more)


      As you can see, the UI thread called the HttpsURLConnectionImpl.disconnect method, which tried to close the ChunkedInputStream. But the close method is synchronized and is waiting for the read method to complete, which never happens. You should probably investigate the possibility to make the ChunkedInputStream.close() method not synchronized.

      I tested that the problem reproduces with JDK 1.7.0_55, 1.8.0_40 and JDK 1.8.0_45. It reproduces with both HTTP and HTTPS. In this case it is related to Chunked encoding, but with normal responses the problem is similar:
      Thread [AWT-EventQueue-0] (Suspended)
      waiting for: KeepAliveStream (id=16)
      KeepAliveStream(MeteredStream).available() line: 170
      KeepAliveStream.close() line: 85
      HttpURLConnection$HttpInputStream(FilterInputStream).close() line: 181
      HttpURLConnection$HttpInputStream.close() line: 3123
      HttpURLConnection.disconnect() line: 2560
      ... (more)

      This time, the KeepAliveStream.close() calls MeteredStream.available(), which is again synchronized.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Test code:

      import java.io.*;
      import java.net.*;

      public class Test {
      public static void main(String[] args) throws InterruptedException {
      final HttpURLConnection[] conn = new HttpURLConnection[1];

      Runnable task = new Runnable() {
      @Override
      public void run() {
      try {
      URL url = new URL("http://localhost:7080/HangingServer/hangingserver");
      conn[0] = (HttpURLConnection) url.openConnection();
      conn[0].connect();
      InputStream is = conn[0].getInputStream();
      int ch;
      while ((ch = is.read()) >= 0)
      System.out.print((char) ch);
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      };

      new Thread(task).start();

      // wait to start the thread
      Thread.sleep(100);

      conn[0].disconnect();
      }
      }



      The URL has to be one that sends some data in Transfer-encoding: chunked and then stop sending. This is my example servlet that does this:

      import java.io.*;
      import javax.servlet.http.*;

      public class HangingServerServlet extends HttpServlet {
      public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
      try {
      // send some data to cause chunked response to be commited
      resp.getWriter().print("data\n");
      resp.getWriter().flush(); // flush to cause Chunked response

      // now take a sleep
      Thread.sleep(5000);

      // send some more data, after this, the client will be able to disconnect
      resp.getWriter().print("data\n");
      resp.getWriter().flush();

      // now take a second sleep
      Thread.sleep(5000);

      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      }


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The connection should disconnect promptly.
      ACTUAL -
      If using the supplied HangingServlet, it disconnects after 5 seconds. In my real case, no more data is sent in reasonable time, so it's hung.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      CUSTOMER SUBMITTED WORKAROUND :
      Use a a short timeout and ignore timeout exception and just retry the read.

        Attachments

          Activity

            People

            • Assignee:
              igerasim Ivan Gerasimov (Inactive)
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: